| // Copyright 2012 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 "ui/gfx/animation/keyframe/keyframed_animation_curve.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/numerics/ranges.h" |
| #include "base/time/time.h" |
| #include "ui/gfx/animation/keyframe/keyframed_animation_curve-inl.h" |
| #include "ui/gfx/animation/tween.h" |
| #include "ui/gfx/geometry/box_f.h" |
| |
| namespace gfx { |
| namespace { |
| |
| static constexpr float kTolerance = 1e-5f; |
| |
| template <typename KeyframeType, typename ValueType, typename TargetType> |
| std::unique_ptr<AnimationCurve> RetargettedCurve( |
| std::vector<std::unique_ptr<KeyframeType>>& keyframes, |
| base::TimeDelta t, |
| const ValueType& value_at_t, |
| const ValueType& new_target_value, |
| double scaled_duration, |
| TargetType* target, |
| const TimingFunction* timing_function) { |
| if (SufficientlyEqual(keyframes.back()->Value(), new_target_value)) |
| return nullptr; |
| |
| DCHECK_GE(keyframes.size(), 2u); |
| DCHECK_GT(scaled_duration, 0.f); |
| |
| // If we haven't progressed to animating between the last 2 keyframes, simply |
| // clobber the value for the last keyframe. |
| const bool at_last_keyframe = |
| (keyframes[keyframes.size() - 2]->Time() * scaled_duration) <= t; |
| if (!at_last_keyframe) { |
| auto& last_keyframe = keyframes.back(); |
| auto* keyframe_timing_function = last_keyframe->timing_function(); |
| last_keyframe = KeyframeType::Create( |
| last_keyframe->Time(), new_target_value, |
| keyframe_timing_function ? keyframe_timing_function->Clone() : nullptr); |
| return nullptr; |
| } |
| |
| // Ensure that `t` happens between the last two keyframes. |
| DCHECK_GE(keyframes[keyframes.size() - 1]->Time() * scaled_duration, t); |
| |
| // TODO(crbug.com/1198305): This can be changed to a different / special |
| // interpolation curve type to maintain c2 continuity. |
| auto curve = AnimationTraits<ValueType>::KeyframedCurveType::Create(); |
| curve->set_scaled_duration(scaled_duration); |
| curve->set_target(target); |
| |
| auto generate_timing_function = |
| [timing_function]() -> std::unique_ptr<gfx::TimingFunction> { |
| if (timing_function) |
| return timing_function->Clone(); |
| return nullptr; |
| }; |
| |
| // Keep the curve duration the same by adding the same first frame. |
| curve->AddKeyframe(KeyframeType::Create(keyframes.front()->Time(), |
| keyframes.front()->Value(), |
| generate_timing_function())); |
| |
| // Snap the current value at `t` so that the current value stays the same. |
| curve->AddKeyframe(KeyframeType::Create(t / scaled_duration, value_at_t, |
| generate_timing_function())); |
| |
| // Add a new target at the same time as the last frame. |
| curve->AddKeyframe(KeyframeType::Create( |
| keyframes.back()->Time(), new_target_value, generate_timing_function())); |
| |
| return curve; |
| } |
| |
| } // namespace |
| |
| Keyframe::Keyframe(base::TimeDelta time, |
| std::unique_ptr<TimingFunction> timing_function) |
| : time_(time), timing_function_(std::move(timing_function)) {} |
| |
| Keyframe::~Keyframe() = default; |
| |
| base::TimeDelta Keyframe::Time() const { |
| return time_; |
| } |
| |
| std::unique_ptr<ColorKeyframe> ColorKeyframe::Create( |
| base::TimeDelta time, |
| SkColor value, |
| std::unique_ptr<TimingFunction> timing_function) { |
| return base::WrapUnique( |
| new ColorKeyframe(time, value, std::move(timing_function))); |
| } |
| |
| ColorKeyframe::ColorKeyframe(base::TimeDelta time, |
| SkColor value, |
| std::unique_ptr<TimingFunction> timing_function) |
| : Keyframe(time, std::move(timing_function)), value_(value) {} |
| |
| ColorKeyframe::~ColorKeyframe() = default; |
| |
| SkColor ColorKeyframe::Value() const { |
| return value_; |
| } |
| |
| std::unique_ptr<ColorKeyframe> ColorKeyframe::Clone() const { |
| std::unique_ptr<TimingFunction> func; |
| if (timing_function()) |
| func = timing_function()->Clone(); |
| return ColorKeyframe::Create(Time(), Value(), std::move(func)); |
| } |
| |
| std::unique_ptr<FloatKeyframe> FloatKeyframe::Create( |
| base::TimeDelta time, |
| float value, |
| std::unique_ptr<TimingFunction> timing_function) { |
| return base::WrapUnique( |
| new FloatKeyframe(time, value, std::move(timing_function))); |
| } |
| |
| FloatKeyframe::FloatKeyframe(base::TimeDelta time, |
| float value, |
| std::unique_ptr<TimingFunction> timing_function) |
| : Keyframe(time, std::move(timing_function)), value_(value) {} |
| |
| FloatKeyframe::~FloatKeyframe() = default; |
| |
| float FloatKeyframe::Value() const { |
| return value_; |
| } |
| |
| std::unique_ptr<FloatKeyframe> FloatKeyframe::Clone() const { |
| std::unique_ptr<TimingFunction> func; |
| if (timing_function()) |
| func = timing_function()->Clone(); |
| return FloatKeyframe::Create(Time(), Value(), std::move(func)); |
| } |
| |
| std::unique_ptr<TransformKeyframe> TransformKeyframe::Create( |
| base::TimeDelta time, |
| const gfx::TransformOperations& value, |
| std::unique_ptr<TimingFunction> timing_function) { |
| return base::WrapUnique( |
| new TransformKeyframe(time, value, std::move(timing_function))); |
| } |
| |
| TransformKeyframe::TransformKeyframe( |
| base::TimeDelta time, |
| const gfx::TransformOperations& value, |
| std::unique_ptr<TimingFunction> timing_function) |
| : Keyframe(time, std::move(timing_function)), value_(value) {} |
| |
| TransformKeyframe::~TransformKeyframe() = default; |
| |
| const gfx::TransformOperations& TransformKeyframe::Value() const { |
| return value_; |
| } |
| |
| std::unique_ptr<TransformKeyframe> TransformKeyframe::Clone() const { |
| std::unique_ptr<TimingFunction> func; |
| if (timing_function()) |
| func = timing_function()->Clone(); |
| return TransformKeyframe::Create(Time(), Value(), std::move(func)); |
| } |
| |
| std::unique_ptr<SizeKeyframe> SizeKeyframe::Create( |
| base::TimeDelta time, |
| const gfx::SizeF& value, |
| std::unique_ptr<TimingFunction> timing_function) { |
| return base::WrapUnique( |
| new SizeKeyframe(time, value, std::move(timing_function))); |
| } |
| |
| SizeKeyframe::SizeKeyframe(base::TimeDelta time, |
| const gfx::SizeF& value, |
| std::unique_ptr<TimingFunction> timing_function) |
| : Keyframe(time, std::move(timing_function)), value_(value) {} |
| |
| SizeKeyframe::~SizeKeyframe() = default; |
| |
| const gfx::SizeF& SizeKeyframe::Value() const { |
| return value_; |
| } |
| |
| std::unique_ptr<SizeKeyframe> SizeKeyframe::Clone() const { |
| std::unique_ptr<TimingFunction> func; |
| if (timing_function()) |
| func = timing_function()->Clone(); |
| return SizeKeyframe::Create(Time(), Value(), std::move(func)); |
| } |
| |
| std::unique_ptr<RectKeyframe> RectKeyframe::Create( |
| base::TimeDelta time, |
| const gfx::Rect& value, |
| std::unique_ptr<TimingFunction> timing_function) { |
| return base::WrapUnique( |
| new RectKeyframe(time, value, std::move(timing_function))); |
| } |
| |
| RectKeyframe::RectKeyframe(base::TimeDelta time, |
| const gfx::Rect& value, |
| std::unique_ptr<TimingFunction> timing_function) |
| : Keyframe(time, std::move(timing_function)), value_(value) {} |
| |
| RectKeyframe::~RectKeyframe() = default; |
| |
| const gfx::Rect& RectKeyframe::Value() const { |
| return value_; |
| } |
| |
| std::unique_ptr<RectKeyframe> RectKeyframe::Clone() const { |
| std::unique_ptr<TimingFunction> func; |
| if (timing_function()) |
| func = timing_function()->Clone(); |
| return RectKeyframe::Create(Time(), Value(), std::move(func)); |
| } |
| |
| std::unique_ptr<KeyframedColorAnimationCurve> |
| KeyframedColorAnimationCurve::Create() { |
| return base::WrapUnique(new KeyframedColorAnimationCurve); |
| } |
| |
| KeyframedColorAnimationCurve::KeyframedColorAnimationCurve() |
| : scaled_duration_(1.0) {} |
| |
| KeyframedColorAnimationCurve::~KeyframedColorAnimationCurve() = default; |
| |
| void KeyframedColorAnimationCurve::AddKeyframe( |
| std::unique_ptr<ColorKeyframe> keyframe) { |
| InsertKeyframe(std::move(keyframe), &keyframes_); |
| } |
| |
| base::TimeDelta KeyframedColorAnimationCurve::Duration() const { |
| return (keyframes_.back()->Time() - keyframes_.front()->Time()) * |
| scaled_duration(); |
| } |
| |
| base::TimeDelta KeyframedColorAnimationCurve::TickInterval() const { |
| return ComputeTickInterval(timing_function_, scaled_duration(), keyframes_); |
| } |
| |
| std::unique_ptr<AnimationCurve> KeyframedColorAnimationCurve::Clone() const { |
| std::unique_ptr<KeyframedColorAnimationCurve> to_return = |
| KeyframedColorAnimationCurve::Create(); |
| for (const auto& keyframe : keyframes_) |
| to_return->AddKeyframe(keyframe->Clone()); |
| |
| if (timing_function_) |
| to_return->SetTimingFunction(timing_function_->Clone()); |
| |
| to_return->set_scaled_duration(scaled_duration()); |
| |
| return std::move(to_return); |
| } |
| |
| SkColor KeyframedColorAnimationCurve::GetValue(base::TimeDelta t) const { |
| if (t <= (keyframes_.front()->Time() * scaled_duration())) |
| return keyframes_.front()->Value(); |
| |
| if (t >= (keyframes_.back()->Time() * scaled_duration())) |
| return keyframes_.back()->Value(); |
| |
| t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(), |
| t); |
| size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t); |
| double progress = |
| TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i); |
| |
| return gfx::Tween::ColorValueBetween(progress, keyframes_[i]->Value(), |
| keyframes_[i + 1]->Value()); |
| } |
| |
| std::unique_ptr<AnimationCurve> KeyframedColorAnimationCurve::Retarget( |
| base::TimeDelta t, |
| SkColor new_target) { |
| DCHECK(!keyframes_.empty()); |
| return RetargettedCurve(keyframes_, t, GetValue(t), new_target, |
| scaled_duration(), target(), timing_function_.get()); |
| } |
| |
| std::unique_ptr<KeyframedFloatAnimationCurve> |
| KeyframedFloatAnimationCurve::Create() { |
| return base::WrapUnique(new KeyframedFloatAnimationCurve); |
| } |
| |
| KeyframedFloatAnimationCurve::KeyframedFloatAnimationCurve() |
| : scaled_duration_(1.0) {} |
| |
| KeyframedFloatAnimationCurve::~KeyframedFloatAnimationCurve() = default; |
| |
| void KeyframedFloatAnimationCurve::AddKeyframe( |
| std::unique_ptr<FloatKeyframe> keyframe) { |
| InsertKeyframe(std::move(keyframe), &keyframes_); |
| } |
| |
| base::TimeDelta KeyframedFloatAnimationCurve::Duration() const { |
| return (keyframes_.back()->Time() - keyframes_.front()->Time()) * |
| scaled_duration(); |
| } |
| |
| base::TimeDelta KeyframedFloatAnimationCurve::TickInterval() const { |
| return ComputeTickInterval(timing_function_, scaled_duration(), keyframes_); |
| } |
| |
| std::unique_ptr<AnimationCurve> KeyframedFloatAnimationCurve::Clone() const { |
| std::unique_ptr<KeyframedFloatAnimationCurve> to_return = |
| KeyframedFloatAnimationCurve::Create(); |
| for (const auto& keyframe : keyframes_) |
| to_return->AddKeyframe(keyframe->Clone()); |
| |
| if (timing_function_) |
| to_return->SetTimingFunction(timing_function_->Clone()); |
| |
| to_return->set_scaled_duration(scaled_duration()); |
| |
| return std::move(to_return); |
| } |
| |
| std::unique_ptr<AnimationCurve> KeyframedFloatAnimationCurve::Retarget( |
| base::TimeDelta t, |
| float new_target) { |
| DCHECK(!keyframes_.empty()); |
| return RetargettedCurve(keyframes_, t, GetValue(t), new_target, |
| scaled_duration(), target(), timing_function_.get()); |
| } |
| |
| float KeyframedFloatAnimationCurve::GetValue(base::TimeDelta t) const { |
| if (t <= (keyframes_.front()->Time() * scaled_duration())) |
| return keyframes_.front()->Value(); |
| |
| if (t >= (keyframes_.back()->Time() * scaled_duration())) |
| return keyframes_.back()->Value(); |
| |
| t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(), |
| t); |
| size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t); |
| double progress = |
| TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i); |
| |
| return keyframes_[i]->Value() + |
| (keyframes_[i + 1]->Value() - keyframes_[i]->Value()) * progress; |
| } |
| |
| std::unique_ptr<KeyframedTransformAnimationCurve> |
| KeyframedTransformAnimationCurve::Create() { |
| return base::WrapUnique(new KeyframedTransformAnimationCurve); |
| } |
| |
| KeyframedTransformAnimationCurve::KeyframedTransformAnimationCurve() |
| : scaled_duration_(1.0) {} |
| |
| KeyframedTransformAnimationCurve::~KeyframedTransformAnimationCurve() = default; |
| |
| void KeyframedTransformAnimationCurve::AddKeyframe( |
| std::unique_ptr<TransformKeyframe> keyframe) { |
| InsertKeyframe(std::move(keyframe), &keyframes_); |
| } |
| |
| base::TimeDelta KeyframedTransformAnimationCurve::Duration() const { |
| return (keyframes_.back()->Time() - keyframes_.front()->Time()) * |
| scaled_duration(); |
| } |
| |
| base::TimeDelta KeyframedTransformAnimationCurve::TickInterval() const { |
| return ComputeTickInterval(timing_function_, scaled_duration(), keyframes_); |
| } |
| |
| std::unique_ptr<AnimationCurve> KeyframedTransformAnimationCurve::Clone() |
| const { |
| std::unique_ptr<KeyframedTransformAnimationCurve> to_return = |
| KeyframedTransformAnimationCurve::Create(); |
| for (const auto& keyframe : keyframes_) |
| to_return->AddKeyframe(keyframe->Clone()); |
| |
| if (timing_function_) |
| to_return->SetTimingFunction(timing_function_->Clone()); |
| |
| to_return->set_scaled_duration(scaled_duration()); |
| |
| return std::move(to_return); |
| } |
| |
| gfx::TransformOperations KeyframedTransformAnimationCurve::GetValue( |
| base::TimeDelta t) const { |
| if (t <= (keyframes_.front()->Time() * scaled_duration())) |
| return keyframes_.front()->Value(); |
| |
| if (t >= (keyframes_.back()->Time() * scaled_duration())) |
| return keyframes_.back()->Value(); |
| |
| t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(), |
| t); |
| size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t); |
| double progress = |
| TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i); |
| |
| return keyframes_[i + 1]->Value().Blend(keyframes_[i]->Value(), progress); |
| } |
| |
| bool KeyframedTransformAnimationCurve::PreservesAxisAlignment() const { |
| for (const auto& keyframe : keyframes_) { |
| if (!keyframe->Value().PreservesAxisAlignment()) |
| return false; |
| } |
| return true; |
| } |
| |
| bool KeyframedTransformAnimationCurve::MaximumScale(float* max_scale) const { |
| DCHECK_GE(keyframes_.size(), 2ul); |
| *max_scale = 0.f; |
| for (auto& keyframe : keyframes_) { |
| float keyframe_scale = 0.f; |
| if (!keyframe->Value().ScaleComponent(&keyframe_scale)) |
| continue; |
| *max_scale = std::max(*max_scale, keyframe_scale); |
| } |
| return *max_scale > 0.f; |
| } |
| |
| std::unique_ptr<AnimationCurve> KeyframedTransformAnimationCurve::Retarget( |
| base::TimeDelta t, |
| const gfx::TransformOperations& new_target) { |
| DCHECK(!keyframes_.empty()); |
| return RetargettedCurve(keyframes_, t, GetValue(t), new_target, |
| scaled_duration(), target(), timing_function_.get()); |
| } |
| |
| std::unique_ptr<KeyframedSizeAnimationCurve> |
| KeyframedSizeAnimationCurve::Create() { |
| return base::WrapUnique(new KeyframedSizeAnimationCurve); |
| } |
| |
| KeyframedSizeAnimationCurve::KeyframedSizeAnimationCurve() |
| : scaled_duration_(1.0) {} |
| |
| KeyframedSizeAnimationCurve::~KeyframedSizeAnimationCurve() = default; |
| |
| void KeyframedSizeAnimationCurve::AddKeyframe( |
| std::unique_ptr<SizeKeyframe> keyframe) { |
| InsertKeyframe(std::move(keyframe), &keyframes_); |
| } |
| |
| base::TimeDelta KeyframedSizeAnimationCurve::Duration() const { |
| return (keyframes_.back()->Time() - keyframes_.front()->Time()) * |
| scaled_duration(); |
| } |
| |
| base::TimeDelta KeyframedSizeAnimationCurve::TickInterval() const { |
| return ComputeTickInterval(timing_function_, scaled_duration(), keyframes_); |
| } |
| |
| std::unique_ptr<AnimationCurve> KeyframedSizeAnimationCurve::Clone() const { |
| std::unique_ptr<KeyframedSizeAnimationCurve> to_return = |
| KeyframedSizeAnimationCurve::Create(); |
| for (const auto& keyframe : keyframes_) |
| to_return->AddKeyframe(keyframe->Clone()); |
| |
| if (timing_function_) |
| to_return->SetTimingFunction(timing_function_->Clone()); |
| |
| to_return->set_scaled_duration(scaled_duration()); |
| |
| return std::move(to_return); |
| } |
| |
| gfx::SizeF KeyframedSizeAnimationCurve::GetValue(base::TimeDelta t) const { |
| if (t <= (keyframes_.front()->Time() * scaled_duration())) |
| return keyframes_.front()->Value(); |
| |
| if (t >= (keyframes_.back()->Time() * scaled_duration())) |
| return keyframes_.back()->Value(); |
| |
| t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(), |
| t); |
| size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t); |
| double progress = |
| TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i); |
| |
| return gfx::Tween::SizeFValueBetween(progress, keyframes_[i]->Value(), |
| keyframes_[i + 1]->Value()); |
| } |
| |
| std::unique_ptr<AnimationCurve> KeyframedSizeAnimationCurve::Retarget( |
| base::TimeDelta t, |
| const gfx::SizeF& new_target) { |
| DCHECK(!keyframes_.empty()); |
| return RetargettedCurve(keyframes_, t, GetValue(t), new_target, |
| scaled_duration(), target(), timing_function_.get()); |
| } |
| |
| std::unique_ptr<KeyframedRectAnimationCurve> |
| KeyframedRectAnimationCurve::Create() { |
| return base::WrapUnique(new KeyframedRectAnimationCurve); |
| } |
| |
| KeyframedRectAnimationCurve::KeyframedRectAnimationCurve() |
| : scaled_duration_(1.0) {} |
| |
| KeyframedRectAnimationCurve::~KeyframedRectAnimationCurve() = default; |
| |
| void KeyframedRectAnimationCurve::AddKeyframe( |
| std::unique_ptr<RectKeyframe> keyframe) { |
| InsertKeyframe(std::move(keyframe), &keyframes_); |
| } |
| |
| base::TimeDelta KeyframedRectAnimationCurve::Duration() const { |
| return (keyframes_.back()->Time() - keyframes_.front()->Time()) * |
| scaled_duration(); |
| } |
| |
| base::TimeDelta KeyframedRectAnimationCurve::TickInterval() const { |
| return ComputeTickInterval(timing_function_, scaled_duration(), keyframes_); |
| } |
| |
| std::unique_ptr<AnimationCurve> KeyframedRectAnimationCurve::Clone() const { |
| std::unique_ptr<KeyframedRectAnimationCurve> to_return = |
| KeyframedRectAnimationCurve::Create(); |
| for (const auto& keyframe : keyframes_) |
| to_return->AddKeyframe(keyframe->Clone()); |
| |
| if (timing_function_) |
| to_return->SetTimingFunction(timing_function_->Clone()); |
| |
| to_return->set_scaled_duration(scaled_duration()); |
| |
| return std::move(to_return); |
| } |
| |
| gfx::Rect KeyframedRectAnimationCurve::GetValue(base::TimeDelta t) const { |
| if (t <= (keyframes_.front()->Time() * scaled_duration())) |
| return keyframes_.front()->Value(); |
| |
| if (t >= (keyframes_.back()->Time() * scaled_duration())) |
| return keyframes_.back()->Value(); |
| |
| t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(), |
| t); |
| size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t); |
| double progress = |
| TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i); |
| |
| return gfx::Tween::RectValueBetween(progress, keyframes_[i]->Value(), |
| keyframes_[i + 1]->Value()); |
| } |
| |
| std::unique_ptr<AnimationCurve> KeyframedRectAnimationCurve::Retarget( |
| base::TimeDelta t, |
| const gfx::Rect& new_target) { |
| DCHECK(!keyframes_.empty()); |
| return RetargettedCurve(keyframes_, t, GetValue(t), new_target, |
| scaled_duration(), target(), timing_function_.get()); |
| } |
| |
| bool SufficientlyEqual(float lhs, float rhs) { |
| return base::IsApproximatelyEqual(lhs, rhs, kTolerance); |
| } |
| |
| bool SufficientlyEqual(const TransformOperations& lhs, |
| const TransformOperations& rhs) { |
| return lhs.ApproximatelyEqual(rhs, kTolerance); |
| } |
| |
| bool SufficientlyEqual(const SizeF& lhs, const SizeF& rhs) { |
| return base::IsApproximatelyEqual(lhs.width(), rhs.width(), kTolerance) && |
| base::IsApproximatelyEqual(lhs.height(), rhs.height(), kTolerance); |
| } |
| |
| bool SufficientlyEqual(SkColor lhs, SkColor rhs) { |
| return lhs == rhs; |
| } |
| |
| bool SufficientlyEqual(const Rect& lhs, const Rect& rhs) { |
| return lhs == rhs; |
| } |
| |
| } // namespace gfx |