| // Copyright 2017 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/gfx/animation/keyframe/keyframe_effect.h" |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/gfx/animation/keyframe/animation_curve.h" |
| #include "ui/gfx/animation/keyframe/keyframed_animation_curve.h" |
| #include "ui/gfx/animation/keyframe/test/animation_utils.h" |
| #include "ui/gfx/geometry/test/geometry_util.h" |
| |
| namespace gfx { |
| |
| static constexpr float kNoise = 1e-6f; |
| static constexpr float kEpsilon = 1e-5f; |
| |
| // Tests client-specific property ids. |
| static constexpr int kLayoutOffsetPropertyId = 19; |
| static constexpr int kBackgroundColorPropertyId = 20; |
| static constexpr int kOpacityPropertyId = 21; |
| static constexpr int kBoundsPropertyId = 22; |
| static constexpr int kTransformPropertyId = 23; |
| static constexpr int kRectPropertyId = 24; |
| |
| class TestAnimationTarget : public SizeAnimationCurve::Target, |
| public TransformAnimationCurve::Target, |
| public FloatAnimationCurve::Target, |
| public ColorAnimationCurve::Target, |
| public RectAnimationCurve::Target { |
| public: |
| TestAnimationTarget() { |
| layout_offset_.AppendTranslate(0, 0, 0); |
| operations_.AppendTranslate(0, 0, 0); |
| operations_.AppendRotate(1, 0, 0, 0); |
| operations_.AppendScale(1, 1, 1); |
| } |
| |
| const SizeF& size() const { return size_; } |
| const TransformOperations& operations() const { return operations_; } |
| const TransformOperations& layout_offset() const { return layout_offset_; } |
| float opacity() const { return opacity_; } |
| SkColor background_color() const { return background_color_; } |
| Rect rect() const { return rect_; } |
| |
| void OnSizeAnimated(const SizeF& size, |
| int target_property_id, |
| KeyframeModel* keyframe_model) override { |
| size_ = size; |
| } |
| |
| void OnTransformAnimated(const TransformOperations& operations, |
| int target_property_id, |
| KeyframeModel* keyframe_model) override { |
| if (target_property_id == kLayoutOffsetPropertyId) { |
| layout_offset_ = operations; |
| } else { |
| operations_ = operations; |
| } |
| } |
| |
| void OnFloatAnimated(const float& opacity, |
| int target_property_id, |
| KeyframeModel* keyframe_model) override { |
| opacity_ = opacity; |
| } |
| |
| void OnColorAnimated(const SkColor& color, |
| int target_property_id, |
| KeyframeModel* keyframe_model) override { |
| background_color_ = color; |
| } |
| |
| void OnRectAnimated(const Rect& rect, |
| int target_property_id, |
| KeyframeModel* keyframe_model) override { |
| rect_ = rect; |
| } |
| |
| private: |
| TransformOperations layout_offset_; |
| TransformOperations operations_; |
| SizeF size_ = {10.0f, 10.0f}; |
| float opacity_ = 1.0f; |
| SkColor background_color_ = SK_ColorRED; |
| Rect rect_; |
| }; |
| |
| TEST(KeyframeAnimationTest, AddRemoveKeyframeModels) { |
| KeyframeEffect animator; |
| EXPECT_TRUE(animator.keyframe_models().empty()); |
| TestAnimationTarget target; |
| |
| animator.AddKeyframeModel(CreateSizeAnimation(&target, 1, kBoundsPropertyId, |
| SizeF(10, 100), SizeF(20, 200), |
| MicrosecondsToDelta(10000))); |
| EXPECT_EQ(1ul, animator.keyframe_models().size()); |
| EXPECT_EQ(kBoundsPropertyId, animator.keyframe_models()[0]->TargetProperty()); |
| |
| TransformOperations from_operations; |
| from_operations.AppendTranslate(10, 100, 1000); |
| TransformOperations to_operations; |
| to_operations.AppendTranslate(20, 200, 2000); |
| animator.AddKeyframeModel(CreateTransformAnimation( |
| &target, 2, kTransformPropertyId, from_operations, to_operations, |
| MicrosecondsToDelta(10000))); |
| |
| EXPECT_EQ(2ul, animator.keyframe_models().size()); |
| EXPECT_EQ(kTransformPropertyId, |
| animator.keyframe_models()[1]->TargetProperty()); |
| |
| animator.AddKeyframeModel(CreateTransformAnimation( |
| &target, 3, kTransformPropertyId, from_operations, to_operations, |
| MicrosecondsToDelta(10000))); |
| EXPECT_EQ(3ul, animator.keyframe_models().size()); |
| EXPECT_EQ(kTransformPropertyId, |
| animator.keyframe_models()[2]->TargetProperty()); |
| |
| animator.RemoveKeyframeModels(kTransformPropertyId); |
| EXPECT_EQ(1ul, animator.keyframe_models().size()); |
| EXPECT_EQ(kBoundsPropertyId, animator.keyframe_models()[0]->TargetProperty()); |
| |
| animator.RemoveKeyframeModel(animator.keyframe_models()[0]->id()); |
| EXPECT_TRUE(animator.keyframe_models().empty()); |
| } |
| |
| TEST(KeyframeAnimationTest, AnimationLifecycle) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| |
| animator.AddKeyframeModel(CreateSizeAnimation(&target, 1, kBoundsPropertyId, |
| SizeF(10, 100), SizeF(20, 200), |
| MicrosecondsToDelta(10000))); |
| EXPECT_EQ(1ul, animator.keyframe_models().size()); |
| EXPECT_EQ(kBoundsPropertyId, animator.keyframe_models()[0]->TargetProperty()); |
| EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, |
| animator.keyframe_models()[0]->run_state()); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1); |
| animator.Tick(start_time); |
| EXPECT_EQ(KeyframeModel::RUNNING, animator.keyframe_models()[0]->run_state()); |
| |
| EXPECT_SIZEF_EQ(SizeF(10, 100), target.size()); |
| |
| // Tick beyond the animation |
| animator.Tick(start_time + MicrosecondsToDelta(20000)); |
| |
| EXPECT_TRUE(animator.keyframe_models().empty()); |
| |
| // Should have assumed the final value. |
| EXPECT_SIZEF_EQ(SizeF(20, 200), target.size()); |
| } |
| |
| TEST(KeyframeAnimationTest, AnimationQueue) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| |
| animator.AddKeyframeModel(CreateSizeAnimation(&target, 1, kBoundsPropertyId, |
| SizeF(10, 100), SizeF(20, 200), |
| MicrosecondsToDelta(10000))); |
| EXPECT_EQ(1ul, animator.keyframe_models().size()); |
| EXPECT_EQ(kBoundsPropertyId, animator.keyframe_models()[0]->TargetProperty()); |
| EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, |
| animator.keyframe_models()[0]->run_state()); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1); |
| animator.Tick(start_time); |
| EXPECT_EQ(KeyframeModel::RUNNING, animator.keyframe_models()[0]->run_state()); |
| EXPECT_SIZEF_EQ(SizeF(10, 100), target.size()); |
| |
| animator.AddKeyframeModel(CreateSizeAnimation(&target, 2, kBoundsPropertyId, |
| SizeF(10, 100), SizeF(20, 200), |
| MicrosecondsToDelta(10000))); |
| |
| TransformOperations from_operations; |
| from_operations.AppendTranslate(10, 100, 1000); |
| TransformOperations to_operations; |
| to_operations.AppendTranslate(20, 200, 2000); |
| animator.AddKeyframeModel(CreateTransformAnimation( |
| &target, 3, kTransformPropertyId, from_operations, to_operations, |
| MicrosecondsToDelta(10000))); |
| |
| EXPECT_EQ(3ul, animator.keyframe_models().size()); |
| EXPECT_EQ(kBoundsPropertyId, animator.keyframe_models()[1]->TargetProperty()); |
| EXPECT_EQ(kTransformPropertyId, |
| animator.keyframe_models()[2]->TargetProperty()); |
| int id1 = animator.keyframe_models()[1]->id(); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(1)); |
| |
| // Only the transform animation should have started (since there's no |
| // conflicting animation). |
| EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, |
| animator.keyframe_models()[1]->run_state()); |
| EXPECT_EQ(KeyframeModel::RUNNING, animator.keyframe_models()[2]->run_state()); |
| |
| // Tick beyond the first animator. This should cause it (and the transform |
| // animation) to get removed and for the second bounds animation to start. |
| animator.Tick(start_time + MicrosecondsToDelta(15000)); |
| |
| EXPECT_EQ(1ul, animator.keyframe_models().size()); |
| EXPECT_EQ(KeyframeModel::RUNNING, animator.keyframe_models()[0]->run_state()); |
| EXPECT_EQ(id1, animator.keyframe_models()[0]->id()); |
| |
| // Tick beyond all animations. There should be none remaining. |
| animator.Tick(start_time + MicrosecondsToDelta(30000)); |
| EXPECT_TRUE(animator.keyframe_models().empty()); |
| } |
| |
| TEST(KeyframeAnimationTest, FinishedTransition) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| Transition transition; |
| transition.target_properties = {kOpacityPropertyId}; |
| transition.duration = MsToDelta(10); |
| animator.set_transition(transition); |
| |
| base::TimeTicks start_time = MsToTicks(1000); |
| animator.Tick(start_time); |
| |
| float from = 1.0f; |
| float to = 0.0f; |
| animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, to); |
| |
| animator.Tick(start_time); |
| EXPECT_EQ(from, target.opacity()); |
| |
| // We now simulate a long pause where the element hasn't been ticked (eg, it |
| // may have been hidden). If this happens, the unticked transition must still |
| // be treated as having finished. |
| animator.TransitionFloatTo(&target, start_time + MsToDelta(1000), |
| kOpacityPropertyId, target.opacity(), 1.0f); |
| |
| animator.Tick(start_time + MsToDelta(1000)); |
| EXPECT_EQ(to, target.opacity()); |
| } |
| |
| TEST(KeyframeAnimationTest, OpacityTransitions) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| Transition transition; |
| transition.target_properties = {kOpacityPropertyId}; |
| transition.duration = MicrosecondsToDelta(10000); |
| animator.set_transition(transition); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| |
| float from = 1.0f; |
| float to = 0.5f; |
| animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, to); |
| |
| EXPECT_EQ(from, target.opacity()); |
| animator.Tick(start_time); |
| |
| // Scheduling a redundant, approximately equal transition should be ignored. |
| int keyframe_model_id = animator.keyframe_models().front()->id(); |
| float nearby = to + kNoise; |
| animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, |
| nearby); |
| EXPECT_EQ(keyframe_model_id, animator.keyframe_models().front()->id()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| EXPECT_GT(from, target.opacity()); |
| EXPECT_LT(to, target.opacity()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(10000)); |
| EXPECT_EQ(to, target.opacity()); |
| } |
| |
| TEST(KeyframeAnimationTest, ReversedOpacityTransitions) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| Transition transition; |
| transition.target_properties = {kOpacityPropertyId}; |
| transition.duration = MicrosecondsToDelta(10000); |
| animator.set_transition(transition); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| |
| float from = 1.0f; |
| float to = 0.5f; |
| animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, to); |
| |
| EXPECT_EQ(from, target.opacity()); |
| animator.Tick(start_time); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(1000)); |
| float value_before_reversing = target.opacity(); |
| EXPECT_GT(from, value_before_reversing); |
| EXPECT_LT(to, value_before_reversing); |
| |
| animator.TransitionFloatTo(&target, start_time + MicrosecondsToDelta(1000), |
| kOpacityPropertyId, target.opacity(), from); |
| animator.Tick(start_time + MicrosecondsToDelta(1000)); |
| EXPECT_FLOAT_EQ(value_before_reversing, target.opacity()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(2000)); |
| EXPECT_EQ(from, target.opacity()); |
| } |
| |
| TEST(KeyframeAnimationTest, RetargetOpacityTransition) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| |
| std::unique_ptr<KeyframedFloatAnimationCurve> curve( |
| gfx::KeyframedFloatAnimationCurve::Create()); |
| curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 1.0f, nullptr)); |
| curve->AddKeyframe( |
| FloatKeyframe::Create(MicrosecondsToDelta(10000), 0.0f, nullptr)); |
| curve->set_target(&target); |
| animator.AddKeyframeModel(KeyframeModel::Create( |
| std::move(curve), KeyframeEffect::GetNextKeyframeModelId(), |
| kOpacityPropertyId)); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| EXPECT_EQ(1.f, target.opacity()); |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| EXPECT_FLOAT_EQ(0.5f, target.opacity()); |
| |
| animator.GetKeyframeModel(kOpacityPropertyId) |
| ->Retarget(start_time + MicrosecondsToDelta(5000), kOpacityPropertyId, |
| 1.f); |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| EXPECT_FLOAT_EQ(0.5f, target.opacity()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(7500)); |
| EXPECT_FLOAT_EQ(0.75f, target.opacity()); |
| } |
| |
| TEST(KeyframeAnimationTest, RetargetTransitionBeforeLastKeyframe) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| |
| std::unique_ptr<KeyframedFloatAnimationCurve> curve( |
| gfx::KeyframedFloatAnimationCurve::Create()); |
| curve->AddKeyframe(FloatKeyframe::Create(base::TimeDelta(), 1.0f, nullptr)); |
| curve->AddKeyframe( |
| FloatKeyframe::Create(MicrosecondsToDelta(5000), 0.5f, nullptr)); |
| curve->AddKeyframe( |
| FloatKeyframe::Create(MicrosecondsToDelta(10000), 0.0f, nullptr)); |
| curve->set_target(&target); |
| animator.AddKeyframeModel(KeyframeModel::Create( |
| std::move(curve), KeyframeEffect::GetNextKeyframeModelId(), |
| kOpacityPropertyId)); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| EXPECT_EQ(1.f, target.opacity()); |
| |
| animator.GetKeyframeModel(kOpacityPropertyId) |
| ->Retarget(start_time + MicrosecondsToDelta(4000), kOpacityPropertyId, |
| 0.1f); |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| EXPECT_FLOAT_EQ(0.5f, target.opacity()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(7500)); |
| EXPECT_FLOAT_EQ(0.3f, target.opacity()); |
| } |
| |
| TEST(KeyframeAnimationTest, LayoutOffsetTransitions) { |
| // In this test, we do expect exact equality. |
| float tolerance = 0.0f; |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| Transition transition; |
| transition.target_properties = {kLayoutOffsetPropertyId}; |
| transition.duration = MicrosecondsToDelta(10000); |
| animator.set_transition(transition); |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| |
| TransformOperations from = target.layout_offset(); |
| |
| TransformOperations to; |
| to.AppendTranslate(8, 0, 0); |
| |
| animator.TransitionTransformOperationsTo(&target, start_time, |
| kLayoutOffsetPropertyId, from, to); |
| |
| EXPECT_TRUE(from.ApproximatelyEqual(target.layout_offset(), tolerance)); |
| animator.Tick(start_time); |
| |
| // Scheduling a redundant, approximately equal transition should be ignored. |
| int keyframe_model_id = animator.keyframe_models().front()->id(); |
| TransformOperations nearby = to; |
| nearby.at(0).translate.x += kNoise; |
| animator.TransitionTransformOperationsTo( |
| &target, start_time, kLayoutOffsetPropertyId, from, nearby); |
| EXPECT_EQ(keyframe_model_id, animator.keyframe_models().front()->id()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| EXPECT_LT(from.at(0).translate.x, target.layout_offset().at(0).translate.x); |
| EXPECT_GT(to.at(0).translate.x, target.layout_offset().at(0).translate.x); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(10000)); |
| EXPECT_TRUE(to.ApproximatelyEqual(target.layout_offset(), tolerance)); |
| } |
| |
| TEST(KeyframeAnimationTest, TransformTransitions) { |
| // In this test, we do expect exact equality. |
| float tolerance = 0.0f; |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| Transition transition; |
| transition.target_properties = {kTransformPropertyId}; |
| transition.duration = MicrosecondsToDelta(10000); |
| animator.set_transition(transition); |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| |
| TransformOperations from = target.operations(); |
| |
| TransformOperations to; |
| to.AppendTranslate(8, 0, 0); |
| to.AppendRotate(1, 0, 0, 0); |
| to.AppendScale(1, 1, 1); |
| |
| animator.TransitionTransformOperationsTo(&target, start_time, |
| kTransformPropertyId, from, to); |
| |
| EXPECT_TRUE(from.ApproximatelyEqual(target.operations(), tolerance)); |
| animator.Tick(start_time); |
| |
| // Scheduling a redundant, approximately equal transition should be ignored. |
| int keyframe_model_id = animator.keyframe_models().front()->id(); |
| TransformOperations nearby = to; |
| nearby.at(0).translate.x += kNoise; |
| animator.TransitionTransformOperationsTo(&target, start_time, |
| kTransformPropertyId, from, nearby); |
| EXPECT_EQ(keyframe_model_id, animator.keyframe_models().front()->id()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| EXPECT_LT(from.at(0).translate.x, target.operations().at(0).translate.x); |
| EXPECT_GT(to.at(0).translate.x, target.operations().at(0).translate.x); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(10000)); |
| EXPECT_TRUE(to.ApproximatelyEqual(target.operations(), tolerance)); |
| } |
| |
| TEST(KeyframeAnimationTest, ReversedTransformTransitions) { |
| // In this test, we do expect exact equality. |
| float tolerance = 0.0f; |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| Transition transition; |
| transition.target_properties = {kTransformPropertyId}; |
| transition.duration = MicrosecondsToDelta(10000); |
| animator.set_transition(transition); |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| |
| TransformOperations from = target.operations(); |
| |
| TransformOperations to; |
| to.AppendTranslate(8, 0, 0); |
| to.AppendRotate(1, 0, 0, 0); |
| to.AppendScale(1, 1, 1); |
| |
| animator.TransitionTransformOperationsTo(&target, start_time, |
| kTransformPropertyId, from, to); |
| |
| EXPECT_TRUE(from.ApproximatelyEqual(target.operations(), tolerance)); |
| animator.Tick(start_time); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(1000)); |
| TransformOperations value_before_reversing = target.operations(); |
| EXPECT_LT(from.at(0).translate.x, target.operations().at(0).translate.x); |
| EXPECT_GT(to.at(0).translate.x, target.operations().at(0).translate.x); |
| |
| animator.TransitionTransformOperationsTo( |
| &target, start_time + MicrosecondsToDelta(1000), kTransformPropertyId, |
| target.operations(), from); |
| animator.Tick(start_time + MicrosecondsToDelta(1000)); |
| EXPECT_TRUE(value_before_reversing.ApproximatelyEqual(target.operations(), |
| tolerance)); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(2000)); |
| EXPECT_TRUE(from.ApproximatelyEqual(target.operations(), tolerance)); |
| } |
| |
| TEST(KeyframeAnimationTest, RetargetTransformTransition) { |
| float tolerance = 0.0f; |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| |
| TransformOperations from; |
| from.AppendScale(1, 1, 1); |
| from.AppendTranslate(0, 0, 0); |
| TransformOperations to; |
| to.AppendScale(11, 11, 11); |
| to.AppendTranslate(-10, -10, -10); |
| |
| std::unique_ptr<KeyframedTransformAnimationCurve> curve( |
| gfx::KeyframedTransformAnimationCurve::Create()); |
| curve->AddKeyframe( |
| TransformKeyframe::Create(base::TimeDelta(), from, nullptr)); |
| curve->AddKeyframe( |
| TransformKeyframe::Create(MicrosecondsToDelta(10000), to, nullptr)); |
| curve->set_target(&target); |
| animator.AddKeyframeModel(KeyframeModel::Create( |
| std::move(curve), KeyframeEffect::GetNextKeyframeModelId(), |
| kTransformPropertyId)); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| EXPECT_TRUE(from.ApproximatelyEqual(target.operations(), tolerance)); |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| |
| EXPECT_FLOAT_EQ(6.f, target.operations().at(0).scale.x); |
| EXPECT_FLOAT_EQ(-5.f, target.operations().at(1).translate.x); |
| |
| TransformOperations new_to; |
| new_to.AppendScale(110, 110, 110); |
| new_to.AppendTranslate(-101, -101, -101); |
| |
| animator.GetKeyframeModel(kTransformPropertyId) |
| ->Retarget(start_time + MicrosecondsToDelta(5000), kTransformPropertyId, |
| new_to); |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| EXPECT_FLOAT_EQ(6.f, target.operations().at(0).scale.x); |
| EXPECT_FLOAT_EQ(-5.f, target.operations().at(1).translate.x); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(7500)); |
| EXPECT_FLOAT_EQ(58.f, target.operations().at(0).scale.x); |
| EXPECT_FLOAT_EQ(-53.f, target.operations().at(1).translate.x); |
| } |
| |
| TEST(KeyframeAnimationTest, BoundsTransitions) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| Transition transition; |
| transition.target_properties = {kBoundsPropertyId}; |
| transition.duration = MicrosecondsToDelta(10000); |
| animator.set_transition(transition); |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| |
| SizeF from = target.size(); |
| SizeF to(20.0f, 20.0f); |
| |
| animator.TransitionSizeTo(&target, start_time, kBoundsPropertyId, from, to); |
| |
| EXPECT_SIZEF_EQ(from, target.size()); |
| animator.Tick(start_time); |
| |
| // Scheduling a redundant, approximately equal transition should be ignored. |
| int keyframe_model_id = animator.keyframe_models().front()->id(); |
| SizeF nearby = to; |
| nearby.set_width(to.width() + kNoise); |
| animator.TransitionSizeTo(&target, start_time, kBoundsPropertyId, from, |
| nearby); |
| EXPECT_EQ(keyframe_model_id, animator.keyframe_models().front()->id()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| EXPECT_LT(from.width(), target.size().width()); |
| EXPECT_GT(to.width(), target.size().width()); |
| EXPECT_LT(from.height(), target.size().height()); |
| EXPECT_GT(to.height(), target.size().height()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(10000)); |
| EXPECT_SIZEF_EQ(to, target.size()); |
| } |
| |
| TEST(KeyframeAnimationTest, RetargetSizeTransition) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| |
| SizeF from(1, 2); |
| SizeF to(11, 22); |
| |
| std::unique_ptr<KeyframedSizeAnimationCurve> curve( |
| gfx::KeyframedSizeAnimationCurve::Create()); |
| curve->AddKeyframe(SizeKeyframe::Create(base::TimeDelta(), from, nullptr)); |
| curve->AddKeyframe( |
| SizeKeyframe::Create(MicrosecondsToDelta(10000), to, nullptr)); |
| curve->set_target(&target); |
| animator.AddKeyframeModel(KeyframeModel::Create( |
| std::move(curve), KeyframeEffect::GetNextKeyframeModelId(), |
| kBoundsPropertyId)); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| EXPECT_EQ(from, target.size()); |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| |
| EXPECT_SIZEF_EQ(SizeF(6, 12), target.size()); |
| |
| SizeF new_to(600, 1200); |
| |
| animator.GetKeyframeModel(kBoundsPropertyId) |
| ->Retarget(start_time + MicrosecondsToDelta(5000), kRectPropertyId, |
| new_to); |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| EXPECT_SIZEF_EQ(SizeF(6, 12), target.size()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(7500)); |
| EXPECT_SIZEF_EQ(SizeF(303, 606), target.size()); |
| } |
| |
| TEST(KeyframeAnimationTest, ReversedBoundsTransitions) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| Transition transition; |
| transition.target_properties = {kBoundsPropertyId}; |
| transition.duration = MicrosecondsToDelta(10000); |
| animator.set_transition(transition); |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| |
| SizeF from = target.size(); |
| SizeF to(20.0f, 20.0f); |
| |
| animator.TransitionSizeTo(&target, start_time, kBoundsPropertyId, from, to); |
| |
| EXPECT_SIZEF_EQ(from, target.size()); |
| animator.Tick(start_time); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(1000)); |
| SizeF value_before_reversing = target.size(); |
| EXPECT_LT(from.width(), target.size().width()); |
| EXPECT_GT(to.width(), target.size().width()); |
| EXPECT_LT(from.height(), target.size().height()); |
| EXPECT_GT(to.height(), target.size().height()); |
| |
| animator.TransitionSizeTo(&target, start_time + MicrosecondsToDelta(1000), |
| kBoundsPropertyId, target.size(), from); |
| animator.Tick(start_time + MicrosecondsToDelta(1000)); |
| EXPECT_SIZEF_EQ(value_before_reversing, target.size()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(2000)); |
| EXPECT_SIZEF_EQ(from, target.size()); |
| } |
| |
| TEST(KeyframeAnimationTest, BackgroundColorTransitions) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| Transition transition; |
| transition.target_properties = {kBackgroundColorPropertyId}; |
| transition.duration = MicrosecondsToDelta(10000); |
| animator.set_transition(transition); |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| |
| SkColor from = SK_ColorRED; |
| SkColor to = SK_ColorGREEN; |
| |
| animator.TransitionColorTo(&target, start_time, kBackgroundColorPropertyId, |
| from, to); |
| |
| EXPECT_EQ(from, target.background_color()); |
| animator.Tick(start_time); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| EXPECT_GT(SkColorGetR(from), SkColorGetR(target.background_color())); |
| EXPECT_LT(SkColorGetR(to), SkColorGetR(target.background_color())); |
| EXPECT_LT(SkColorGetG(from), SkColorGetG(target.background_color())); |
| EXPECT_GT(SkColorGetG(to), SkColorGetG(target.background_color())); |
| EXPECT_EQ(0u, SkColorGetB(target.background_color())); |
| EXPECT_EQ(255u, SkColorGetA(target.background_color())); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(10000)); |
| EXPECT_EQ(to, target.background_color()); |
| } |
| |
| TEST(KeyframeAnimationTest, ReversedBackgroundColorTransitions) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| Transition transition; |
| transition.target_properties = {kBackgroundColorPropertyId}; |
| transition.duration = MicrosecondsToDelta(10000); |
| animator.set_transition(transition); |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| |
| SkColor from = SK_ColorRED; |
| SkColor to = SK_ColorGREEN; |
| |
| animator.TransitionColorTo(&target, start_time, kBackgroundColorPropertyId, |
| from, to); |
| |
| EXPECT_EQ(from, target.background_color()); |
| animator.Tick(start_time); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(1000)); |
| SkColor value_before_reversing = target.background_color(); |
| EXPECT_GT(SkColorGetR(from), SkColorGetR(target.background_color())); |
| EXPECT_LT(SkColorGetR(to), SkColorGetR(target.background_color())); |
| EXPECT_LT(SkColorGetG(from), SkColorGetG(target.background_color())); |
| EXPECT_GT(SkColorGetG(to), SkColorGetG(target.background_color())); |
| EXPECT_EQ(0u, SkColorGetB(target.background_color())); |
| EXPECT_EQ(255u, SkColorGetA(target.background_color())); |
| |
| animator.TransitionColorTo(&target, start_time + MicrosecondsToDelta(1000), |
| kBackgroundColorPropertyId, |
| target.background_color(), from); |
| animator.Tick(start_time + MicrosecondsToDelta(1000)); |
| EXPECT_EQ(value_before_reversing, target.background_color()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(2000)); |
| EXPECT_EQ(from, target.background_color()); |
| } |
| |
| TEST(KeyframeAnimationTest, RetargetColorTransition) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| |
| SkColor from = SkColorSetRGB(0, 0, 0); |
| SkColor to = SkColorSetRGB(10, 10, 10); |
| |
| std::unique_ptr<KeyframedColorAnimationCurve> curve( |
| gfx::KeyframedColorAnimationCurve::Create()); |
| curve->AddKeyframe(ColorKeyframe::Create(base::TimeDelta(), from, nullptr)); |
| curve->AddKeyframe( |
| ColorKeyframe::Create(MicrosecondsToDelta(10000), to, nullptr)); |
| curve->set_target(&target); |
| animator.AddKeyframeModel(KeyframeModel::Create( |
| std::move(curve), KeyframeEffect::GetNextKeyframeModelId(), |
| kBackgroundColorPropertyId)); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| EXPECT_EQ(from, target.background_color()); |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| |
| EXPECT_EQ(5u, SkColorGetR(target.background_color())); |
| |
| SkColor new_to = SkColorSetRGB(101, 101, 101); |
| |
| animator.GetKeyframeModel(kBackgroundColorPropertyId) |
| ->Retarget(start_time + MicrosecondsToDelta(5000), kRectPropertyId, |
| new_to); |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| EXPECT_EQ(5u, SkColorGetR(target.background_color())); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(7500)); |
| EXPECT_EQ(53u, SkColorGetR(target.background_color())); |
| } |
| |
| TEST(KeyframeAnimationTest, DoubleReversedTransitions) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| Transition transition; |
| transition.target_properties = {kOpacityPropertyId}; |
| transition.duration = MicrosecondsToDelta(10000); |
| animator.set_transition(transition); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| |
| float from = 1.0f; |
| float to = 0.5f; |
| animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, to); |
| |
| EXPECT_EQ(from, target.opacity()); |
| animator.Tick(start_time); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(1000)); |
| float value_before_reversing = target.opacity(); |
| EXPECT_GT(from, value_before_reversing); |
| EXPECT_LT(to, value_before_reversing); |
| |
| animator.TransitionFloatTo(&target, start_time + MicrosecondsToDelta(1000), |
| kOpacityPropertyId, target.opacity(), from); |
| animator.Tick(start_time + MicrosecondsToDelta(1000)); |
| EXPECT_FLOAT_EQ(value_before_reversing, target.opacity()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(1500)); |
| value_before_reversing = target.opacity(); |
| // If the code for reversing transitions does not account for an existing time |
| // offset, then reversing a second time will give incorrect values. |
| animator.TransitionFloatTo(&target, start_time + MicrosecondsToDelta(1500), |
| kOpacityPropertyId, target.opacity(), to); |
| animator.Tick(start_time + MicrosecondsToDelta(1500)); |
| EXPECT_FLOAT_EQ(value_before_reversing, target.opacity()); |
| } |
| |
| TEST(KeyframeAnimationTest, RedundantTransition) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| Transition transition; |
| transition.target_properties = {kOpacityPropertyId}; |
| transition.duration = MicrosecondsToDelta(10000); |
| animator.set_transition(transition); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| |
| float from = 1.0f; |
| float to = 0.5f; |
| animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, to); |
| |
| EXPECT_EQ(from, target.opacity()); |
| animator.Tick(start_time); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(1000)); |
| float value_before_redundant_transition = target.opacity(); |
| |
| // While an existing transition is in progress to the same value, we should |
| // not start a new transition. |
| animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, |
| target.opacity(), to); |
| |
| EXPECT_EQ(1lu, animator.keyframe_models().size()); |
| EXPECT_EQ(value_before_redundant_transition, target.opacity()); |
| } |
| |
| TEST(KeyframeAnimationTest, TransitionToSameValue) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| Transition transition; |
| transition.target_properties = {kOpacityPropertyId}; |
| transition.duration = MicrosecondsToDelta(10000); |
| animator.set_transition(transition); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| |
| // Transitioning to the same value should be a no-op. |
| float from = 1.0f; |
| float to = 1.0f; |
| animator.TransitionFloatTo(&target, start_time, kOpacityPropertyId, from, to); |
| EXPECT_EQ(from, target.opacity()); |
| EXPECT_TRUE(animator.keyframe_models().empty()); |
| } |
| |
| TEST(KeyframeAnimationTest, CorrectTargetValue) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| base::TimeDelta duration = MicrosecondsToDelta(10000); |
| |
| float from_opacity = 1.0f; |
| float to_opacity = 0.5f; |
| SizeF from_bounds = SizeF(10, 200); |
| SizeF to_bounds = SizeF(20, 200); |
| SkColor from_color = SK_ColorRED; |
| SkColor to_color = SK_ColorGREEN; |
| TransformOperations from_transform; |
| from_transform.AppendTranslate(10, 100, 1000); |
| TransformOperations to_transform; |
| to_transform.AppendTranslate(20, 200, 2000); |
| |
| // Verify the default value is returned if there's no running animations. |
| EXPECT_EQ(from_opacity, |
| animator.GetTargetFloatValue(kOpacityPropertyId, from_opacity)); |
| EXPECT_SIZEF_EQ(from_bounds, |
| animator.GetTargetSizeValue(kBoundsPropertyId, from_bounds)); |
| EXPECT_EQ(from_color, animator.GetTargetColorValue(kBackgroundColorPropertyId, |
| from_color)); |
| EXPECT_TRUE(from_transform.ApproximatelyEqual( |
| animator.GetTargetTransformOperationsValue(kTransformPropertyId, |
| from_transform), |
| kEpsilon)); |
| |
| // Add keyframe_models. |
| animator.AddKeyframeModel(CreateFloatAnimation( |
| &target, 2, kOpacityPropertyId, from_opacity, to_opacity, duration)); |
| animator.AddKeyframeModel(CreateSizeAnimation( |
| &target, 1, kBoundsPropertyId, from_bounds, to_bounds, duration)); |
| animator.AddKeyframeModel(CreateColorAnimation( |
| &target, 3, kBackgroundColorPropertyId, from_color, to_color, duration)); |
| animator.AddKeyframeModel( |
| CreateTransformAnimation(&target, 4, kTransformPropertyId, from_transform, |
| to_transform, duration)); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| |
| // Verify target value. |
| EXPECT_EQ(to_opacity, |
| animator.GetTargetFloatValue(kOpacityPropertyId, from_opacity)); |
| EXPECT_SIZEF_EQ(to_bounds, |
| animator.GetTargetSizeValue(kBoundsPropertyId, from_bounds)); |
| EXPECT_EQ(to_color, animator.GetTargetColorValue(kBackgroundColorPropertyId, |
| from_color)); |
| EXPECT_TRUE(to_transform.ApproximatelyEqual( |
| animator.GetTargetTransformOperationsValue(kTransformPropertyId, |
| from_transform), |
| kEpsilon)); |
| } |
| |
| TEST(KeyframeAnimationTest, RetargetRectTransition) { |
| TestAnimationTarget target; |
| KeyframeEffect animator; |
| |
| Rect from(1, 2, 3, 4); |
| Rect to(11, 22, 33, 44); |
| |
| std::unique_ptr<KeyframedRectAnimationCurve> curve( |
| gfx::KeyframedRectAnimationCurve::Create()); |
| curve->AddKeyframe(RectKeyframe::Create(base::TimeDelta(), from, nullptr)); |
| curve->AddKeyframe( |
| RectKeyframe::Create(MicrosecondsToDelta(10000), to, nullptr)); |
| curve->set_target(&target); |
| animator.AddKeyframeModel(KeyframeModel::Create( |
| std::move(curve), KeyframeEffect::GetNextKeyframeModelId(), |
| kRectPropertyId)); |
| |
| base::TimeTicks start_time = MicrosecondsToTicks(1000000); |
| animator.Tick(start_time); |
| EXPECT_EQ(from, target.rect()); |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| |
| EXPECT_EQ(Rect(6, 12, 18, 24), target.rect()); |
| |
| Rect new_to(600, 1200, 1800, 2400); |
| |
| animator.GetKeyframeModel(kRectPropertyId) |
| ->Retarget(start_time + MicrosecondsToDelta(5000), kRectPropertyId, |
| new_to); |
| animator.Tick(start_time + MicrosecondsToDelta(5000)); |
| EXPECT_EQ(Rect(6, 12, 18, 24), target.rect()); |
| |
| animator.Tick(start_time + MicrosecondsToDelta(7500)); |
| EXPECT_EQ(Rect(303, 606, 909, 1212), target.rect()); |
| } |
| |
| } // namespace gfx |