| // 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/element_animations.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "cc/animation/animation_delegate.h" |
| #include "cc/animation/animation_events.h" |
| #include "cc/animation/animation_host.h" |
| #include "cc/animation/animation_player.h" |
| #include "cc/animation/keyframed_animation_curve.h" |
| #include "cc/base/filter_operations.h" |
| #include "cc/trees/mutator_host_client.h" |
| #include "ui/gfx/geometry/box_f.h" |
| |
| namespace cc { |
| |
| scoped_refptr<ElementAnimations> ElementAnimations::Create() { |
| return make_scoped_refptr(new ElementAnimations()); |
| } |
| |
| ElementAnimations::ElementAnimations() |
| : animation_host_(), |
| element_id_(), |
| has_element_in_active_list_(false), |
| has_element_in_pending_list_(false), |
| needs_push_properties_(false), |
| needs_update_impl_client_state_(false) {} |
| |
| ElementAnimations::~ElementAnimations() {} |
| |
| void ElementAnimations::SetAnimationHost(AnimationHost* host) { |
| animation_host_ = host; |
| } |
| |
| void ElementAnimations::SetElementId(ElementId element_id) { |
| element_id_ = element_id; |
| } |
| |
| void ElementAnimations::InitAffectedElementTypes() { |
| DCHECK(element_id_); |
| DCHECK(animation_host_); |
| |
| UpdatePlayersTickingState(UpdateTickingType::FORCE); |
| |
| DCHECK(animation_host_->mutator_host_client()); |
| if (animation_host_->mutator_host_client()->IsElementInList( |
| element_id_, ElementListType::ACTIVE)) { |
| set_has_element_in_active_list(true); |
| } |
| if (animation_host_->mutator_host_client()->IsElementInList( |
| element_id_, ElementListType::PENDING)) { |
| set_has_element_in_pending_list(true); |
| } |
| } |
| |
| TargetProperties ElementAnimations::GetPropertiesMaskForAnimationState() { |
| TargetProperties properties; |
| properties[TargetProperty::TRANSFORM] = true; |
| properties[TargetProperty::OPACITY] = true; |
| properties[TargetProperty::FILTER] = true; |
| return properties; |
| } |
| |
| void ElementAnimations::ClearAffectedElementTypes() { |
| DCHECK(animation_host_); |
| |
| TargetProperties disable_properties = GetPropertiesMaskForAnimationState(); |
| PropertyAnimationState disabled_state_mask, disabled_state; |
| disabled_state_mask.currently_running = disable_properties; |
| disabled_state_mask.potentially_animating = disable_properties; |
| |
| if (has_element_in_active_list()) { |
| animation_host()->mutator_host_client()->ElementIsAnimatingChanged( |
| element_id(), ElementListType::ACTIVE, disabled_state_mask, |
| disabled_state); |
| } |
| set_has_element_in_active_list(false); |
| |
| if (has_element_in_pending_list()) { |
| animation_host()->mutator_host_client()->ElementIsAnimatingChanged( |
| element_id(), ElementListType::PENDING, disabled_state_mask, |
| disabled_state); |
| } |
| set_has_element_in_pending_list(false); |
| |
| RemovePlayersFromTicking(); |
| } |
| |
| void ElementAnimations::ElementRegistered(ElementId element_id, |
| ElementListType list_type) { |
| DCHECK_EQ(element_id_, element_id); |
| |
| if (!has_element_in_any_list()) |
| UpdatePlayersTickingState(UpdateTickingType::FORCE); |
| |
| if (list_type == ElementListType::ACTIVE) |
| set_has_element_in_active_list(true); |
| else |
| set_has_element_in_pending_list(true); |
| } |
| |
| void ElementAnimations::ElementUnregistered(ElementId element_id, |
| ElementListType list_type) { |
| DCHECK_EQ(this->element_id(), element_id); |
| if (list_type == ElementListType::ACTIVE) |
| set_has_element_in_active_list(false); |
| else |
| set_has_element_in_pending_list(false); |
| |
| if (!has_element_in_any_list()) |
| RemovePlayersFromTicking(); |
| } |
| |
| void ElementAnimations::AddPlayer(AnimationPlayer* player) { |
| players_list_.AddObserver(player); |
| } |
| |
| void ElementAnimations::RemovePlayer(AnimationPlayer* player) { |
| players_list_.RemoveObserver(player); |
| } |
| |
| bool ElementAnimations::IsEmpty() const { |
| return !players_list_.might_have_observers(); |
| } |
| |
| void ElementAnimations::SetNeedsPushProperties() { |
| needs_push_properties_ = true; |
| } |
| |
| void ElementAnimations::PushPropertiesTo( |
| scoped_refptr<ElementAnimations> element_animations_impl) const { |
| DCHECK_NE(this, element_animations_impl); |
| |
| if (!needs_push_properties_) |
| return; |
| needs_push_properties_ = false; |
| |
| // Update impl client state. |
| if (needs_update_impl_client_state_) |
| element_animations_impl->UpdateClientAnimationState(); |
| needs_update_impl_client_state_ = false; |
| } |
| |
| void ElementAnimations::UpdatePlayersTickingState( |
| UpdateTickingType update_ticking_type) const { |
| for (auto& player : players_list_) |
| player.UpdateTickingState(update_ticking_type); |
| } |
| |
| void ElementAnimations::RemovePlayersFromTicking() const { |
| for (auto& player : players_list_) |
| player.RemoveFromTicking(); |
| } |
| |
| void ElementAnimations::NotifyAnimationStarted(const AnimationEvent& event) { |
| DCHECK(!event.is_impl_only); |
| for (auto& player : players_list_) { |
| if (player.NotifyAnimationStarted(event)) |
| break; |
| } |
| } |
| |
| void ElementAnimations::NotifyAnimationFinished(const AnimationEvent& event) { |
| DCHECK(!event.is_impl_only); |
| for (auto& player : players_list_) { |
| if (player.NotifyAnimationFinished(event)) |
| break; |
| } |
| } |
| |
| void ElementAnimations::NotifyAnimationTakeover(const AnimationEvent& event) { |
| DCHECK(!event.is_impl_only); |
| DCHECK(event.target_property == TargetProperty::SCROLL_OFFSET); |
| |
| for (auto& player : players_list_) |
| player.NotifyAnimationTakeover(event); |
| } |
| |
| void ElementAnimations::NotifyAnimationAborted(const AnimationEvent& event) { |
| DCHECK(!event.is_impl_only); |
| |
| for (auto& player : players_list_) { |
| if (player.NotifyAnimationAborted(event)) |
| break; |
| } |
| |
| UpdateClientAnimationState(); |
| } |
| |
| void ElementAnimations::NotifyAnimationPropertyUpdate( |
| const AnimationEvent& event) { |
| DCHECK(!event.is_impl_only); |
| bool notify_active_elements = true; |
| bool notify_pending_elements = true; |
| switch (event.target_property) { |
| case TargetProperty::OPACITY: |
| NotifyClientOpacityAnimated(event.opacity, notify_active_elements, |
| notify_pending_elements); |
| break; |
| case TargetProperty::TRANSFORM: |
| NotifyClientTransformAnimated(event.transform, notify_active_elements, |
| notify_pending_elements); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| bool ElementAnimations::HasFilterAnimationThatInflatesBounds() const { |
| for (auto& player : players_list_) { |
| if (player.HasFilterAnimationThatInflatesBounds()) |
| return true; |
| } |
| return false; |
| } |
| |
| bool ElementAnimations::HasTransformAnimationThatInflatesBounds() const { |
| for (auto& player : players_list_) { |
| if (player.HasTransformAnimationThatInflatesBounds()) |
| return true; |
| } |
| return false; |
| } |
| |
| bool ElementAnimations::FilterAnimationBoundsForBox(const gfx::BoxF& box, |
| gfx::BoxF* bounds) const { |
| // TODO(avallee): Implement. |
| return false; |
| } |
| |
| bool ElementAnimations::TransformAnimationBoundsForBox( |
| const gfx::BoxF& box, |
| gfx::BoxF* bounds) const { |
| *bounds = gfx::BoxF(); |
| |
| for (auto& player : players_list_) { |
| if (!player.HasTransformAnimationThatInflatesBounds()) |
| continue; |
| gfx::BoxF player_bounds; |
| bool success = player.TransformAnimationBoundsForBox(box, &player_bounds); |
| if (!success) |
| return false; |
| bounds->Union(player_bounds); |
| } |
| return true; |
| } |
| |
| bool ElementAnimations::HasOnlyTranslationTransforms( |
| ElementListType list_type) const { |
| for (auto& player : players_list_) { |
| if (!player.HasOnlyTranslationTransforms(list_type)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool ElementAnimations::AnimationsPreserveAxisAlignment() const { |
| for (auto& player : players_list_) { |
| if (!player.AnimationsPreserveAxisAlignment()) |
| return false; |
| } |
| return true; |
| } |
| |
| bool ElementAnimations::AnimationStartScale(ElementListType list_type, |
| float* start_scale) const { |
| *start_scale = 0.f; |
| |
| for (auto& player : players_list_) { |
| float player_start_scale = 0.f; |
| bool success = player.AnimationStartScale(list_type, &player_start_scale); |
| if (!success) |
| return false; |
| // Union: a maximum. |
| *start_scale = std::max(*start_scale, player_start_scale); |
| } |
| |
| return true; |
| } |
| |
| bool ElementAnimations::MaximumTargetScale(ElementListType list_type, |
| float* max_scale) const { |
| *max_scale = 0.f; |
| |
| for (auto& player : players_list_) { |
| float player_max_scale = 0.f; |
| bool success = player.MaximumTargetScale(list_type, &player_max_scale); |
| if (!success) |
| return false; |
| // Union: a maximum. |
| *max_scale = std::max(*max_scale, player_max_scale); |
| } |
| |
| return true; |
| } |
| |
| bool ElementAnimations::ScrollOffsetAnimationWasInterrupted() const { |
| for (auto& player : players_list_) { |
| if (player.scroll_offset_animation_was_interrupted()) |
| return true; |
| } |
| return false; |
| } |
| |
| void ElementAnimations::SetNeedsUpdateImplClientState() { |
| needs_update_impl_client_state_ = true; |
| SetNeedsPushProperties(); |
| } |
| |
| void ElementAnimations::NotifyClientOpacityAnimated( |
| float opacity, |
| bool notify_active_elements, |
| bool notify_pending_elements) { |
| if (notify_active_elements && has_element_in_active_list()) |
| OnOpacityAnimated(ElementListType::ACTIVE, opacity); |
| if (notify_pending_elements && has_element_in_pending_list()) |
| OnOpacityAnimated(ElementListType::PENDING, opacity); |
| } |
| |
| void ElementAnimations::NotifyClientTransformAnimated( |
| const gfx::Transform& transform, |
| bool notify_active_elements, |
| bool notify_pending_elements) { |
| if (notify_active_elements && has_element_in_active_list()) |
| OnTransformAnimated(ElementListType::ACTIVE, transform); |
| if (notify_pending_elements && has_element_in_pending_list()) |
| OnTransformAnimated(ElementListType::PENDING, transform); |
| } |
| |
| void ElementAnimations::NotifyClientFilterAnimated( |
| const FilterOperations& filters, |
| bool notify_active_elements, |
| bool notify_pending_elements) { |
| if (notify_active_elements && has_element_in_active_list()) |
| OnFilterAnimated(ElementListType::ACTIVE, filters); |
| if (notify_pending_elements && has_element_in_pending_list()) |
| OnFilterAnimated(ElementListType::PENDING, filters); |
| } |
| |
| void ElementAnimations::NotifyClientScrollOffsetAnimated( |
| const gfx::ScrollOffset& scroll_offset, |
| bool notify_active_elements, |
| bool notify_pending_elements) { |
| if (notify_active_elements && has_element_in_active_list()) |
| OnScrollOffsetAnimated(ElementListType::ACTIVE, scroll_offset); |
| if (notify_pending_elements && has_element_in_pending_list()) |
| OnScrollOffsetAnimated(ElementListType::PENDING, scroll_offset); |
| } |
| |
| void ElementAnimations::UpdateClientAnimationState() { |
| if (!element_id()) |
| return; |
| DCHECK(animation_host()); |
| if (!animation_host()->mutator_host_client()) |
| return; |
| |
| PropertyAnimationState prev_pending = pending_state_; |
| PropertyAnimationState prev_active = active_state_; |
| |
| pending_state_.Clear(); |
| active_state_.Clear(); |
| |
| for (auto& player : players_list_) { |
| PropertyAnimationState player_pending_state, player_active_state; |
| player.GetPropertyAnimationState(&player_pending_state, |
| &player_active_state); |
| pending_state_ |= player_pending_state; |
| active_state_ |= player_active_state; |
| } |
| |
| TargetProperties allowed_properties = GetPropertiesMaskForAnimationState(); |
| PropertyAnimationState allowed_state; |
| allowed_state.currently_running = allowed_properties; |
| allowed_state.potentially_animating = allowed_properties; |
| |
| pending_state_ &= allowed_state; |
| active_state_ &= allowed_state; |
| |
| DCHECK(pending_state_.IsValid()); |
| DCHECK(active_state_.IsValid()); |
| |
| if (has_element_in_active_list() && prev_active != active_state_) { |
| PropertyAnimationState diff_active = prev_active ^ active_state_; |
| animation_host()->mutator_host_client()->ElementIsAnimatingChanged( |
| element_id(), ElementListType::ACTIVE, diff_active, active_state_); |
| } |
| if (has_element_in_pending_list() && prev_pending != pending_state_) { |
| PropertyAnimationState diff_pending = prev_pending ^ pending_state_; |
| animation_host()->mutator_host_client()->ElementIsAnimatingChanged( |
| element_id(), ElementListType::PENDING, diff_pending, pending_state_); |
| } |
| } |
| |
| bool ElementAnimations::HasTickingAnimation() const { |
| for (auto& player : players_list_) { |
| if (player.HasTickingAnimation()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ElementAnimations::HasAnyAnimation() const { |
| for (auto& player : players_list_) { |
| if (player.has_any_animation()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ElementAnimations::HasAnyAnimationTargetingProperty( |
| TargetProperty::Type property) const { |
| for (auto& player : players_list_) { |
| if (player.GetAnimation(property)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool ElementAnimations::IsPotentiallyAnimatingProperty( |
| TargetProperty::Type target_property, |
| ElementListType list_type) const { |
| for (auto& player : players_list_) { |
| if (player.IsPotentiallyAnimatingProperty(target_property, list_type)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ElementAnimations::IsCurrentlyAnimatingProperty( |
| TargetProperty::Type target_property, |
| ElementListType list_type) const { |
| for (auto& player : players_list_) { |
| if (player.IsCurrentlyAnimatingProperty(target_property, list_type)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void ElementAnimations::OnFilterAnimated(ElementListType list_type, |
| const FilterOperations& filters) { |
| DCHECK(element_id()); |
| DCHECK(animation_host()); |
| DCHECK(animation_host()->mutator_host_client()); |
| animation_host()->mutator_host_client()->SetElementFilterMutated( |
| element_id(), list_type, filters); |
| } |
| |
| void ElementAnimations::OnOpacityAnimated(ElementListType list_type, |
| float opacity) { |
| DCHECK(element_id()); |
| DCHECK(animation_host()); |
| DCHECK(animation_host()->mutator_host_client()); |
| animation_host()->mutator_host_client()->SetElementOpacityMutated( |
| element_id(), list_type, opacity); |
| } |
| |
| void ElementAnimations::OnTransformAnimated(ElementListType list_type, |
| const gfx::Transform& transform) { |
| DCHECK(element_id()); |
| DCHECK(animation_host()); |
| DCHECK(animation_host()->mutator_host_client()); |
| animation_host()->mutator_host_client()->SetElementTransformMutated( |
| element_id(), list_type, transform); |
| } |
| |
| void ElementAnimations::OnScrollOffsetAnimated( |
| ElementListType list_type, |
| const gfx::ScrollOffset& scroll_offset) { |
| DCHECK(element_id()); |
| DCHECK(animation_host()); |
| DCHECK(animation_host()->mutator_host_client()); |
| animation_host()->mutator_host_client()->SetElementScrollOffsetMutated( |
| element_id(), list_type, scroll_offset); |
| } |
| |
| gfx::ScrollOffset ElementAnimations::ScrollOffsetForAnimation() const { |
| if (animation_host()) { |
| DCHECK(animation_host()->mutator_host_client()); |
| return animation_host()->mutator_host_client()->GetScrollOffsetForAnimation( |
| element_id()); |
| } |
| |
| return gfx::ScrollOffset(); |
| } |
| |
| } // namespace cc |