| // Copyright 2015 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 "cc/animation/animation.h" |
| |
| #include <inttypes.h> |
| #include <algorithm> |
| |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "cc/animation/animation_delegate.h" |
| #include "cc/animation/animation_events.h" |
| #include "cc/animation/animation_host.h" |
| #include "cc/animation/animation_timeline.h" |
| #include "cc/animation/keyframe_effect.h" |
| #include "cc/animation/scroll_offset_animation_curve.h" |
| #include "cc/animation/transform_operations.h" |
| #include "cc/trees/property_animation_state.h" |
| |
| namespace cc { |
| |
| scoped_refptr<Animation> Animation::Create(int id) { |
| return base::WrapRefCounted(new Animation(id)); |
| } |
| |
| Animation::Animation(int id) |
| : animation_host_(), |
| animation_timeline_(), |
| animation_delegate_(), |
| id_(id), |
| ticking_keyframe_effects_count(0) { |
| DCHECK(id_); |
| } |
| |
| Animation::~Animation() { |
| DCHECK(!animation_timeline_); |
| } |
| |
| scoped_refptr<Animation> Animation::CreateImplInstance() const { |
| return Animation::Create(id()); |
| } |
| |
| void Animation::SetAnimationHost(AnimationHost* animation_host) { |
| animation_host_ = animation_host; |
| } |
| |
| void Animation::SetAnimationTimeline(AnimationTimeline* timeline) { |
| if (animation_timeline_ == timeline) |
| return; |
| |
| // We need to unregister keyframe_effect to manage ElementAnimations and |
| // observers properly. |
| if (!element_to_keyframe_effect_id_map_.empty() && animation_host_) { |
| // Destroy ElementAnimations or release it if it's still needed. |
| UnregisterKeyframeEffects(); |
| } |
| |
| animation_timeline_ = timeline; |
| |
| // Register animation only if layer AND host attached. Unlike the |
| // SingleKeyframeEffectAnimation case, all keyframe_effects have been attached |
| // to their corresponding elements. |
| if (!element_to_keyframe_effect_id_map_.empty() && animation_host_) { |
| RegisterKeyframeEffects(); |
| } |
| } |
| |
| bool Animation::has_element_animations() const { |
| return !element_to_keyframe_effect_id_map_.empty(); |
| } |
| |
| scoped_refptr<ElementAnimations> Animation::element_animations( |
| KeyframeEffectId keyframe_effect_id) const { |
| return GetKeyframeEffectById(keyframe_effect_id)->element_animations(); |
| } |
| |
| void Animation::AttachElementForKeyframeEffect( |
| ElementId element_id, |
| KeyframeEffectId keyframe_effect_id) { |
| DCHECK(GetKeyframeEffectById(keyframe_effect_id)); |
| GetKeyframeEffectById(keyframe_effect_id)->AttachElement(element_id); |
| element_to_keyframe_effect_id_map_[element_id].emplace(keyframe_effect_id); |
| // Register animation only if layer AND host attached. |
| if (animation_host_) { |
| // Create ElementAnimations or re-use existing. |
| RegisterKeyframeEffect(element_id, keyframe_effect_id); |
| } |
| } |
| |
| void Animation::DetachElementForKeyframeEffect( |
| ElementId element_id, |
| KeyframeEffectId keyframe_effect_id) { |
| DCHECK(GetKeyframeEffectById(keyframe_effect_id)); |
| DCHECK_EQ(GetKeyframeEffectById(keyframe_effect_id)->element_id(), |
| element_id); |
| |
| UnregisterKeyframeEffect(element_id, keyframe_effect_id); |
| GetKeyframeEffectById(keyframe_effect_id)->DetachElement(); |
| element_to_keyframe_effect_id_map_[element_id].erase(keyframe_effect_id); |
| } |
| |
| void Animation::DetachElement() { |
| if (animation_host_) { |
| // Destroy ElementAnimations or release it if it's still needed. |
| UnregisterKeyframeEffects(); |
| } |
| |
| for (auto pair = element_to_keyframe_effect_id_map_.begin(); |
| pair != element_to_keyframe_effect_id_map_.end();) { |
| for (auto keyframe_effect = pair->second.begin(); |
| keyframe_effect != pair->second.end();) { |
| GetKeyframeEffectById(*keyframe_effect)->DetachElement(); |
| keyframe_effect = pair->second.erase(keyframe_effect); |
| } |
| pair = element_to_keyframe_effect_id_map_.erase(pair); |
| } |
| DCHECK_EQ(element_to_keyframe_effect_id_map_.size(), 0u); |
| } |
| |
| void Animation::RegisterKeyframeEffect(ElementId element_id, |
| KeyframeEffectId keyframe_effect_id) { |
| DCHECK(animation_host_); |
| KeyframeEffect* keyframe_effect = GetKeyframeEffectById(keyframe_effect_id); |
| DCHECK(!keyframe_effect->has_bound_element_animations()); |
| |
| if (!keyframe_effect->has_attached_element()) |
| return; |
| animation_host_->RegisterKeyframeEffectForElement(element_id, |
| keyframe_effect); |
| } |
| |
| void Animation::UnregisterKeyframeEffect(ElementId element_id, |
| KeyframeEffectId keyframe_effect_id) { |
| DCHECK(animation_host_); |
| KeyframeEffect* keyframe_effect = GetKeyframeEffectById(keyframe_effect_id); |
| DCHECK(keyframe_effect); |
| if (keyframe_effect->has_attached_element() && |
| keyframe_effect->has_bound_element_animations()) { |
| animation_host_->UnregisterKeyframeEffectForElement(element_id, |
| keyframe_effect); |
| } |
| } |
| void Animation::RegisterKeyframeEffects() { |
| for (auto& element_id_keyframe_effect_id : |
| element_to_keyframe_effect_id_map_) { |
| const ElementId element_id = element_id_keyframe_effect_id.first; |
| const std::unordered_set<KeyframeEffectId>& keyframe_effect_ids = |
| element_id_keyframe_effect_id.second; |
| for (auto& keyframe_effect_id : keyframe_effect_ids) |
| RegisterKeyframeEffect(element_id, keyframe_effect_id); |
| } |
| } |
| |
| void Animation::UnregisterKeyframeEffects() { |
| for (auto& element_id_keyframe_effect_id : |
| element_to_keyframe_effect_id_map_) { |
| const ElementId element_id = element_id_keyframe_effect_id.first; |
| const std::unordered_set<KeyframeEffectId>& keyframe_effect_ids = |
| element_id_keyframe_effect_id.second; |
| for (auto& keyframe_effect_id : keyframe_effect_ids) |
| UnregisterKeyframeEffect(element_id, keyframe_effect_id); |
| } |
| animation_host_->RemoveFromTicking(this); |
| } |
| |
| void Animation::PushAttachedKeyframeEffectsToImplThread( |
| Animation* animation_impl) const { |
| for (auto& keyframe_effect : keyframe_effects_) { |
| KeyframeEffect* keyframe_effect_impl = |
| animation_impl->GetKeyframeEffectById(keyframe_effect->id()); |
| if (keyframe_effect_impl) |
| continue; |
| |
| std::unique_ptr<KeyframeEffect> to_add = |
| keyframe_effect->CreateImplInstance(); |
| animation_impl->AddKeyframeEffect(std::move(to_add)); |
| } |
| } |
| |
| void Animation::PushPropertiesToImplThread(Animation* animation_impl) { |
| for (auto& keyframe_effect : keyframe_effects_) { |
| if (KeyframeEffect* keyframe_effect_impl = |
| animation_impl->GetKeyframeEffectById(keyframe_effect->id())) { |
| keyframe_effect->PushPropertiesTo(keyframe_effect_impl); |
| } |
| } |
| } |
| |
| void Animation::AddKeyframeModelForKeyframeEffect( |
| std::unique_ptr<KeyframeModel> keyframe_model, |
| KeyframeEffectId keyframe_effect_id) { |
| DCHECK(GetKeyframeEffectById(keyframe_effect_id)); |
| GetKeyframeEffectById(keyframe_effect_id) |
| ->AddKeyframeModel(std::move(keyframe_model)); |
| } |
| |
| void Animation::PauseKeyframeModelForKeyframeEffect( |
| int keyframe_model_id, |
| double time_offset, |
| KeyframeEffectId keyframe_effect_id) { |
| DCHECK(GetKeyframeEffectById(keyframe_effect_id)); |
| GetKeyframeEffectById(keyframe_effect_id) |
| ->PauseKeyframeModel(keyframe_model_id, time_offset); |
| } |
| |
| void Animation::RemoveKeyframeModelForKeyframeEffect( |
| int keyframe_model_id, |
| KeyframeEffectId keyframe_effect_id) { |
| DCHECK(GetKeyframeEffectById(keyframe_effect_id)); |
| GetKeyframeEffectById(keyframe_effect_id) |
| ->RemoveKeyframeModel(keyframe_model_id); |
| } |
| |
| void Animation::AbortKeyframeModelForKeyframeEffect( |
| int keyframe_model_id, |
| KeyframeEffectId keyframe_effect_id) { |
| DCHECK(GetKeyframeEffectById(keyframe_effect_id)); |
| GetKeyframeEffectById(keyframe_effect_id) |
| ->AbortKeyframeModel(keyframe_model_id); |
| } |
| |
| void Animation::AbortKeyframeModelsWithProperty( |
| TargetProperty::Type target_property, |
| bool needs_completion) { |
| for (auto& keyframe_effect : keyframe_effects_) |
| keyframe_effect->AbortKeyframeModelsWithProperty(target_property, |
| needs_completion); |
| } |
| |
| void Animation::PushPropertiesTo(Animation* animation_impl) { |
| // In general when pushing proerties to impl thread we first push attached |
| // properties to impl followed by removing the detached ones. However, we |
| // never remove individual keyframe effect from an animation so there is no |
| // need to remove the detached ones. |
| PushAttachedKeyframeEffectsToImplThread(animation_impl); |
| PushPropertiesToImplThread(animation_impl); |
| } |
| |
| void Animation::Tick(base::TimeTicks monotonic_time) { |
| DCHECK(!monotonic_time.is_null()); |
| for (auto& keyframe_effect : keyframe_effects_) |
| keyframe_effect->Tick(monotonic_time); |
| } |
| |
| void Animation::UpdateState(bool start_ready_animations, |
| AnimationEvents* events) { |
| for (auto& keyframe_effect : keyframe_effects_) { |
| keyframe_effect->UpdateState(start_ready_animations, events); |
| keyframe_effect->UpdateTickingState(); |
| } |
| } |
| |
| void Animation::AddToTicking() { |
| ++ticking_keyframe_effects_count; |
| if (ticking_keyframe_effects_count > 1) |
| return; |
| DCHECK(animation_host_); |
| animation_host_->AddToTicking(this); |
| } |
| |
| void Animation::RemoveFromTicking() { |
| DCHECK_GE(ticking_keyframe_effects_count, 0); |
| if (!ticking_keyframe_effects_count) |
| return; |
| --ticking_keyframe_effects_count; |
| DCHECK(animation_host_); |
| DCHECK_GE(ticking_keyframe_effects_count, 0); |
| if (ticking_keyframe_effects_count) |
| return; |
| animation_host_->RemoveFromTicking(this); |
| } |
| |
| void Animation::DispatchAndDelegateAnimationEvent(const AnimationEvent& event) { |
| if (event.ShouldDispatchToKeyframeEffectAndModel()) { |
| KeyframeEffect* keyframe_effect = |
| GetKeyframeEffectById(event.uid.effect_id); |
| if (!keyframe_effect || |
| !keyframe_effect->DispatchAnimationEventToKeyframeModel(event)) { |
| // If we fail to dispatch the event, it is to clean up an obsolete |
| // animation and should not notify the delegate. |
| // TODO(gerchiko): Determine when we expect the referenced animations not |
| // to exist. |
| return; |
| } |
| } |
| DelegateAnimationEvent(event); |
| } |
| |
| void Animation::DelegateAnimationEvent(const AnimationEvent& event) { |
| if (animation_delegate_) { |
| switch (event.type) { |
| case AnimationEvent::STARTED: |
| animation_delegate_->NotifyAnimationStarted( |
| event.monotonic_time, event.target_property, event.group_id); |
| break; |
| |
| case AnimationEvent::FINISHED: |
| animation_delegate_->NotifyAnimationFinished( |
| event.monotonic_time, event.target_property, event.group_id); |
| break; |
| |
| case AnimationEvent::ABORTED: |
| animation_delegate_->NotifyAnimationAborted( |
| event.monotonic_time, event.target_property, event.group_id); |
| break; |
| |
| case AnimationEvent::TAKEOVER: |
| // TODO(crbug.com/1018213): Routing TAKEOVER events is broken. |
| DCHECK(!event.is_impl_only); |
| DCHECK(event.target_property == TargetProperty::SCROLL_OFFSET); |
| DCHECK(event.curve); |
| animation_delegate_->NotifyAnimationTakeover( |
| event.monotonic_time, event.target_property, |
| event.animation_start_time, event.curve->Clone()); |
| break; |
| |
| case AnimationEvent::TIME_UPDATED: |
| DCHECK(!event.is_impl_only); |
| animation_delegate_->NotifyLocalTimeUpdated(event.local_time); |
| break; |
| } |
| } |
| } |
| |
| size_t Animation::TickingKeyframeModelsCount() const { |
| size_t count = 0; |
| for (auto& keyframe_effect : keyframe_effects_) |
| count += keyframe_effect->TickingKeyframeModelsCount(); |
| return count; |
| } |
| |
| bool Animation::AffectsCustomProperty() const { |
| for (const auto& keyframe_effect : keyframe_effects_) |
| if (keyframe_effect->AffectsCustomProperty()) |
| return true; |
| return false; |
| } |
| |
| void Animation::SetNeedsCommit() { |
| DCHECK(animation_host_); |
| animation_host_->SetNeedsCommit(); |
| } |
| |
| void Animation::SetNeedsPushProperties() { |
| if (!animation_timeline_) |
| return; |
| animation_timeline_->SetNeedsPushProperties(); |
| } |
| |
| void Animation::ActivateKeyframeEffects() { |
| for (auto& keyframe_effect : keyframe_effects_) { |
| keyframe_effect->ActivateKeyframeEffects(); |
| keyframe_effect->UpdateTickingState(); |
| } |
| } |
| |
| KeyframeModel* Animation::GetKeyframeModelForKeyframeEffect( |
| TargetProperty::Type target_property, |
| KeyframeEffectId keyframe_effect_id) const { |
| DCHECK(GetKeyframeEffectById(keyframe_effect_id)); |
| return GetKeyframeEffectById(keyframe_effect_id) |
| ->GetKeyframeModel(target_property); |
| } |
| |
| std::string Animation::ToString() const { |
| std::string output = base::StringPrintf("Animation{id=%d", id_); |
| for (const auto& keyframe_effect : keyframe_effects_) { |
| output += |
| base::StringPrintf(", element_id=%s, keyframe_models=[%s]", |
| keyframe_effect->element_id().ToString().c_str(), |
| keyframe_effect->KeyframeModelsToString().c_str()); |
| } |
| return output + "}"; |
| } |
| |
| bool Animation::IsWorkletAnimation() const { |
| return false; |
| } |
| |
| void Animation::AddKeyframeEffect( |
| std::unique_ptr<KeyframeEffect> keyframe_effect) { |
| keyframe_effect->SetAnimation(this); |
| keyframe_effects_.push_back(std::move(keyframe_effect)); |
| |
| SetNeedsPushProperties(); |
| } |
| |
| KeyframeEffect* Animation::GetKeyframeEffectById( |
| KeyframeEffectId keyframe_effect_id) const { |
| // May return nullptr when syncing keyframe_effects_ to impl. |
| return keyframe_effects_.size() > keyframe_effect_id |
| ? keyframe_effects_[keyframe_effect_id].get() |
| : nullptr; |
| } |
| |
| } // namespace cc |