| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/views/animation/animation_builder.h" |
| |
| #include <algorithm> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check_op.h" |
| #include "base/debug/alias.h" |
| #include "base/functional/callback.h" |
| #include "base/location.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/no_destructor.h" |
| #include "base/time/time.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/layer_animation_element.h" |
| #include "ui/compositor/layer_animation_observer.h" |
| #include "ui/compositor/layer_animation_sequence.h" |
| #include "ui/compositor/layer_animator.h" |
| #include "ui/compositor/layer_owner.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/views/animation/animation_abort_handle.h" |
| #include "ui/views/animation/animation_key.h" |
| #include "ui/views/animation/animation_sequence_block.h" |
| |
| namespace views { |
| |
| AnimationBuilder::Observer::Observer() = default; |
| |
| AnimationBuilder::Observer::~Observer() { |
| DCHECK(attached_to_sequence_) |
| << "You must register callbacks and get abort handle before " |
| << "creating a sequence block."; |
| if (abort_handle_) { |
| abort_handle_->OnObserverDeleted(); |
| } |
| base::RepeatingClosure& on_observer_deleted = |
| AnimationBuilder::GetObserverDeletedCallback(); |
| if (on_observer_deleted) { |
| on_observer_deleted.Run(); |
| } |
| } |
| |
| void AnimationBuilder::Observer::SetOnStarted(base::OnceClosure callback) { |
| DCHECK(!on_started_); |
| on_started_ = std::move(callback); |
| } |
| |
| void AnimationBuilder::Observer::SetOnEnded(base::OnceClosure callback, |
| base::Location location) { |
| DCHECK(!on_ended_); |
| on_ended_ = std::move(callback); |
| on_ended_location_ = location; |
| } |
| |
| void AnimationBuilder::Observer::SetOnWillRepeat( |
| base::RepeatingClosure callback) { |
| DCHECK(!on_will_repeat_); |
| on_will_repeat_ = std::move(callback); |
| } |
| |
| void AnimationBuilder::Observer::SetOnAborted(base::OnceClosure callback, |
| base::Location location) { |
| DCHECK(!on_aborted_); |
| on_aborted_ = std::move(callback); |
| on_aborted_location_ = location; |
| } |
| |
| void AnimationBuilder::Observer::SetOnScheduled(base::OnceClosure callback) { |
| DCHECK(!on_scheduled_); |
| on_scheduled_ = std::move(callback); |
| } |
| |
| void AnimationBuilder::Observer::OnLayerAnimationStarted( |
| ui::LayerAnimationSequence* sequence) { |
| if (abort_handle_ && abort_handle_->animation_state() == |
| AnimationAbortHandle::AnimationState::kNotStarted) { |
| abort_handle_->OnAnimationStarted(); |
| } |
| if (on_started_) { |
| std::move(on_started_).Run(); |
| } |
| } |
| |
| void AnimationBuilder::Observer::SetAbortHandle( |
| AnimationAbortHandle* abort_handle) { |
| abort_handle_ = abort_handle; |
| } |
| |
| void AnimationBuilder::Observer::OnLayerAnimationEnded( |
| ui::LayerAnimationSequence* sequence) { |
| if (--sequences_to_run_ == 0) { |
| if (on_ended_) { |
| // Ensure that the stack contains information about which on_ended_ |
| // callback this is. Needed to debug a bad callback crash |
| // (https://g-issues.chromium.org/issues/335902543). |
| // TODO(b/335902543): Remove on_ended_location. |
| base::Location on_ended_location = on_ended_location_; |
| base::debug::Alias(&on_ended_location); |
| std::move(on_ended_).Run(); |
| } |
| if (abort_handle_ && abort_handle_->animation_state() == |
| AnimationAbortHandle::AnimationState::kRunning) { |
| abort_handle_->OnAnimationEnded(); |
| } |
| } |
| } |
| |
| void AnimationBuilder::Observer::OnLayerAnimationWillRepeat( |
| ui::LayerAnimationSequence* sequence) { |
| if (!on_will_repeat_) { |
| return; |
| } |
| // First time through, initialize the repeat_map_ with the sequences. |
| if (repeat_map_.empty()) { |
| for (ui::LayerAnimationSequence* seq : attached_sequences()) { |
| repeat_map_[seq] = 0; |
| } |
| } |
| // Only trigger the repeat callback on the last LayerAnimationSequence on |
| // which this observer is attached. |
| const int next_cycle = ++repeat_map_[sequence]; |
| if (std::ranges::none_of( |
| repeat_map_, [next_cycle](int count) { return count < next_cycle; }, |
| &RepeatMap::value_type::second)) { |
| on_will_repeat_.Run(); |
| } |
| } |
| |
| void AnimationBuilder::Observer::OnLayerAnimationAborted( |
| ui::LayerAnimationSequence* sequence) { |
| if (on_aborted_) { |
| // Ensure that the stack contains information about which on_aborted_ |
| // callback this is. Needed to debug a bad callback crash |
| // (https://g-issues.chromium.org/issues/335902543). |
| // TODO(b/335902543): Remove on_aborted_location_. |
| base::Location on_aborted_location = on_aborted_location_; |
| base::debug::Alias(&on_aborted_location); |
| std::move(on_aborted_).Run(); |
| } |
| if (abort_handle_ && abort_handle_->animation_state() == |
| AnimationAbortHandle::AnimationState::kRunning) { |
| abort_handle_->OnAnimationEnded(); |
| } |
| } |
| |
| void AnimationBuilder::Observer::OnLayerAnimationScheduled( |
| ui::LayerAnimationSequence* sequence) { |
| if (on_scheduled_) { |
| std::move(on_scheduled_).Run(); |
| } |
| } |
| |
| void AnimationBuilder::Observer::OnAttachedToSequence( |
| ui::LayerAnimationSequence* sequence) { |
| ui::LayerAnimationObserver::OnAttachedToSequence(sequence); |
| attached_to_sequence_ = true; |
| sequences_to_run_++; |
| } |
| |
| void AnimationBuilder::Observer::OnDetachedFromSequence( |
| ui::LayerAnimationSequence* sequence) { |
| if (attached_sequences().empty()) { |
| delete this; |
| } |
| } |
| |
| bool AnimationBuilder::Observer::RequiresNotificationWhenAnimatorDestroyed() |
| const { |
| return true; |
| } |
| |
| struct AnimationBuilder::Value { |
| base::TimeDelta start; |
| // Save the original duration because the duration on the element can be a |
| // scaled version. The scale can potentially be zero. |
| base::TimeDelta original_duration; |
| std::unique_ptr<ui::LayerAnimationElement> element; |
| |
| auto operator<=>(const Value& rhs) const { |
| // Animations with zero duration need to be ordered before animations with |
| // nonzero of the same start time to prevent the DCHECK from happening in |
| // TerminateSequence(). These animations don't count as overlapping |
| // properties. |
| auto element_properties = element->properties(); |
| auto rhs_element_properties = rhs.element->properties(); |
| return std::tie(start, original_duration, element_properties) <=> |
| std::tie(rhs.start, rhs.original_duration, rhs_element_properties); |
| } |
| |
| bool operator==(const Value& rhs) const { |
| auto element_properties = element->properties(); |
| auto rhs_element_properties = rhs.element->properties(); |
| return std::tie(start, original_duration, element_properties) == |
| std::tie(rhs.start, rhs.original_duration, rhs_element_properties); |
| } |
| }; |
| |
| AnimationBuilder::AnimationBuilder() = default; |
| |
| AnimationBuilder::AnimationBuilder(AnimationBuilder&& rhs) = default; |
| |
| AnimationBuilder& AnimationBuilder::operator=(AnimationBuilder&& rhs) = default; |
| |
| AnimationBuilder::~AnimationBuilder() { |
| // Terminate `current_sequence_` to complete layer animation configuration. |
| current_sequence_.reset(); |
| |
| DCHECK(!next_animation_observer_) |
| << "Callbacks were scheduled without creating a sequence block " |
| "afterwards. There are no animations to run these callbacks on."; |
| // The observer needs to outlive the AnimationBuilder and will manage its own |
| // lifetime. GetAttachedToSequence should not return false here. This is |
| // DCHECKed in the observer’s destructor. |
| if (animation_observer_ && animation_observer_->GetAttachedToSequence()) { |
| animation_observer_.release(); |
| } |
| |
| for (auto it = layer_animation_sequences_.begin(); |
| it != layer_animation_sequences_.end();) { |
| auto* const target = it->first; |
| auto end_it = layer_animation_sequences_.upper_bound(target); |
| |
| if (abort_handle_) { |
| abort_handle_->AddLayer(target); |
| } |
| |
| ui::ScopedLayerAnimationSettings settings(target->GetAnimator()); |
| if (preemption_strategy_) { |
| settings.SetPreemptionStrategy(preemption_strategy_.value()); |
| } |
| std::vector<ui::LayerAnimationSequence*> sequences; |
| std::transform(it, end_it, std::back_inserter(sequences), |
| [](auto& it) { return it.second.release(); }); |
| target->GetAnimator()->StartTogether(std::move(sequences)); |
| it = end_it; |
| } |
| } |
| |
| AnimationBuilder& AnimationBuilder::SetPreemptionStrategy( |
| ui::LayerAnimator::PreemptionStrategy preemption_strategy) { |
| preemption_strategy_ = preemption_strategy; |
| return *this; |
| } |
| |
| AnimationBuilder& AnimationBuilder::OnStarted(base::OnceClosure callback) { |
| GetObserver()->SetOnStarted(std::move(callback)); |
| return *this; |
| } |
| |
| AnimationBuilder& AnimationBuilder::OnEnded(base::OnceClosure callback, |
| base::Location location) { |
| GetObserver()->SetOnEnded(std::move(callback), location); |
| return *this; |
| } |
| |
| AnimationBuilder& AnimationBuilder::OnWillRepeat( |
| base::RepeatingClosure callback) { |
| GetObserver()->SetOnWillRepeat(std::move(callback)); |
| return *this; |
| } |
| |
| AnimationBuilder& AnimationBuilder::OnAborted(base::OnceClosure callback, |
| base::Location location) { |
| GetObserver()->SetOnAborted(std::move(callback), location); |
| return *this; |
| } |
| |
| AnimationBuilder& AnimationBuilder::OnScheduled(base::OnceClosure callback) { |
| GetObserver()->SetOnScheduled(std::move(callback)); |
| return *this; |
| } |
| |
| AnimationSequenceBlock& AnimationBuilder::Once() { |
| return NewSequence(false); |
| } |
| |
| AnimationSequenceBlock& AnimationBuilder::Repeatedly() { |
| return NewSequence(true); |
| } |
| |
| void AnimationBuilder::AddLayerAnimationElement( |
| base::PassKey<AnimationSequenceBlock>, |
| AnimationKey key, |
| base::TimeDelta start, |
| base::TimeDelta original_duration, |
| std::unique_ptr<ui::LayerAnimationElement> element) { |
| auto& values = values_[key]; |
| Value value = {start, original_duration, std::move(element)}; |
| auto it = std::ranges::upper_bound(values, value); |
| values.insert(it, std::move(value)); |
| } |
| |
| std::unique_ptr<AnimationSequenceBlock> AnimationBuilder::SwapCurrentSequence( |
| base::PassKey<AnimationSequenceBlock>, |
| std::unique_ptr<AnimationSequenceBlock> new_sequence) { |
| auto old_sequence = std::move(current_sequence_); |
| current_sequence_ = std::move(new_sequence); |
| return old_sequence; |
| } |
| |
| void AnimationBuilder::BlockEndedAt(base::PassKey<AnimationSequenceBlock>, |
| base::TimeDelta end) { |
| end_ = std::max(end_, end); |
| } |
| |
| void AnimationBuilder::TerminateSequence(base::PassKey<AnimationSequenceBlock>, |
| bool repeating) { |
| for (auto& pair : values_) { |
| auto sequence = std::make_unique<ui::LayerAnimationSequence>(); |
| sequence->set_is_repeating(repeating); |
| if (animation_observer_) { |
| sequence->AddObserver(animation_observer_.get()); |
| } |
| |
| base::TimeDelta start; |
| ui::LayerAnimationElement::AnimatableProperties properties = |
| ui::LayerAnimationElement::UNKNOWN; |
| for (auto& value : pair.second) { |
| DCHECK_GE(value.start, start) |
| << "Do not overlap animations of the same property on the same view."; |
| properties = value.element->properties(); |
| if (value.start > start) { |
| sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement( |
| properties, value.start - start)); |
| start = value.start; |
| } |
| start += value.original_duration; |
| sequence->AddElement(std::move(value.element)); |
| } |
| |
| if (start < end_) { |
| sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement( |
| properties, end_ - start)); |
| } |
| |
| layer_animation_sequences_.insert({pair.first.target, std::move(sequence)}); |
| } |
| |
| values_.clear(); |
| } |
| |
| AnimationSequenceBlock& AnimationBuilder::GetCurrentSequence() { |
| DCHECK(current_sequence_); |
| return *current_sequence_; |
| } |
| |
| std::unique_ptr<AnimationAbortHandle> AnimationBuilder::GetAbortHandle() { |
| DCHECK(!abort_handle_) << "An abort handle is already created."; |
| abort_handle_ = new AnimationAbortHandle(GetObserver()); |
| return base::WrapUnique(abort_handle_.get()); |
| } |
| |
| AnimationBuilder::Observer* AnimationBuilder::GetObserver() { |
| if (!next_animation_observer_) { |
| next_animation_observer_ = std::make_unique<Observer>(); |
| } |
| return next_animation_observer_.get(); |
| } |
| |
| // static |
| void AnimationBuilder::SetObserverDeletedCallbackForTesting( |
| base::RepeatingClosure deleted_closure) { |
| GetObserverDeletedCallback() = std::move(deleted_closure); |
| } |
| |
| AnimationSequenceBlock& AnimationBuilder::NewSequence(bool repeating) { |
| // Each sequence should have its own observer. |
| |
| // Ensure to terminate the current sequence block before touching the |
| // animation sequence observer so that the sequence observer is attached to |
| // the layer animation sequence. |
| if (current_sequence_) { |
| current_sequence_.reset(); |
| } |
| |
| // The observer needs to outlive the AnimationBuilder and will manage its own |
| // lifetime. GetAttachedToSequence should not return false here. This is |
| // DCHECKed in the observer’s destructor. |
| if (animation_observer_ && animation_observer_->GetAttachedToSequence()) { |
| animation_observer_.release(); |
| } |
| if (next_animation_observer_) { |
| animation_observer_ = std::move(next_animation_observer_); |
| next_animation_observer_.reset(); |
| } |
| |
| end_ = base::TimeDelta(); |
| current_sequence_ = std::make_unique<AnimationSequenceBlock>( |
| base::PassKey<AnimationBuilder>(), this, base::TimeDelta(), repeating); |
| return *current_sequence_; |
| } |
| |
| // static |
| base::RepeatingClosure& AnimationBuilder::GetObserverDeletedCallback() { |
| static base::NoDestructor<base::RepeatingClosure> on_observer_deleted; |
| return *on_observer_deleted; |
| } |
| |
| } // namespace views |