123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- // Copyright (c) 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/views/animation/bounds_animator.h"
- #include <algorithm>
- #include <utility>
- #include "base/memory/raw_ptr.h"
- #include "base/run_loop.h"
- #include "base/test/icu_test_util.h"
- #include "base/test/task_environment.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "ui/gfx/animation/slide_animation.h"
- #include "ui/gfx/animation/test_animation_delegate.h"
- #include "ui/views/view.h"
- using gfx::Animation;
- using gfx::SlideAnimation;
- using gfx::TestAnimationDelegate;
- namespace views {
- namespace {
- class OwnedDelegate : public gfx::AnimationDelegate {
- public:
- OwnedDelegate() = default;
- OwnedDelegate(const OwnedDelegate&) = delete;
- OwnedDelegate& operator=(const OwnedDelegate&) = delete;
- ~OwnedDelegate() override { deleted_ = true; }
- static bool GetAndClearDeleted() {
- bool value = deleted_;
- deleted_ = false;
- return value;
- }
- static bool GetAndClearCanceled() {
- bool value = canceled_;
- canceled_ = false;
- return value;
- }
- // Overridden from gfx::AnimationDelegate:
- void AnimationCanceled(const Animation* animation) override {
- canceled_ = true;
- }
- private:
- static bool deleted_;
- static bool canceled_;
- };
- // static
- bool OwnedDelegate::deleted_ = false;
- bool OwnedDelegate::canceled_ = false;
- class TestView : public View {
- public:
- TestView() = default;
- TestView(const TestView&) = delete;
- TestView& operator=(const TestView&) = delete;
- void OnDidSchedulePaint(const gfx::Rect& r) override {
- ++repaint_count_;
- if (dirty_rect_.IsEmpty())
- dirty_rect_ = r;
- else
- dirty_rect_.Union(r);
- }
- const gfx::Rect& dirty_rect() const { return dirty_rect_; }
- void set_repaint_count(int val) { repaint_count_ = val; }
- int repaint_count() const { return repaint_count_; }
- private:
- gfx::Rect dirty_rect_;
- int repaint_count_ = 0;
- };
- class RTLAnimationTestDelegate : public gfx::AnimationDelegate {
- public:
- RTLAnimationTestDelegate(const gfx::Rect& start,
- const gfx::Rect& target,
- View* view,
- base::RepeatingClosure quit_closure)
- : start_(start),
- target_(target),
- view_(view),
- quit_closure_(std::move(quit_closure)) {}
- ~RTLAnimationTestDelegate() override = default;
- private:
- // gfx::AnimationDelegate:
- void AnimationProgressed(const Animation* animation) override {
- gfx::Transform transform = view_->GetTransform();
- ASSERT_TRUE(!transform.IsIdentity());
- // In this test, assume that |parent| is root view.
- View* parent = view_->parent();
- const gfx::Rect start_rect_in_screen = parent->GetMirroredRect(start_);
- const gfx::Rect target_rect_in_screen = parent->GetMirroredRect(target_);
- gfx::RectF current_bounds_in_screen(
- parent->GetMirroredRect(view_->bounds()));
- transform.TransformRect(¤t_bounds_in_screen);
- // Verify that |view_|'s current bounds in screen are valid.
- EXPECT_GE(current_bounds_in_screen.x(),
- std::min(start_rect_in_screen.x(), target_rect_in_screen.x()));
- EXPECT_LE(
- current_bounds_in_screen.right(),
- std::max(start_rect_in_screen.right(), target_rect_in_screen.right()));
- quit_closure_.Run();
- }
- // Animation initial bounds.
- gfx::Rect start_;
- // Animation target bounds.
- gfx::Rect target_;
- // view to be animated.
- raw_ptr<View> view_;
- base::RepeatingClosure quit_closure_;
- };
- } // namespace
- class BoundsAnimatorTest : public testing::Test {
- public:
- BoundsAnimatorTest()
- : task_environment_(
- base::test::TaskEnvironment::TimeSource::MOCK_TIME,
- base::test::SingleThreadTaskEnvironment::MainThreadType::UI),
- child_(new TestView()) {
- parent_.AddChildView(child_.get());
- RecreateAnimator(/*use_transforms=*/false);
- }
- BoundsAnimatorTest(const BoundsAnimatorTest&) = delete;
- BoundsAnimatorTest& operator=(const BoundsAnimatorTest&) = delete;
- TestView* parent() { return &parent_; }
- TestView* child() { return child_; }
- BoundsAnimator* animator() { return animator_.get(); }
- protected:
- void RecreateAnimator(bool use_transforms) {
- animator_ = std::make_unique<BoundsAnimator>(&parent_, use_transforms);
- animator_->SetAnimationDuration(base::Milliseconds(10));
- }
- // Animates |child_| to |target_bounds|. Returns the repaint time.
- // |use_long_duration| indicates whether long or short bounds animation is
- // created.
- int GetRepaintTimeFromBoundsAnimation(const gfx::Rect& target_bounds,
- bool use_long_duration) {
- child()->set_repaint_count(0);
- const base::TimeDelta animation_duration =
- base::Milliseconds(use_long_duration ? 2000 : 10);
- animator()->SetAnimationDuration(animation_duration);
- animator()->AnimateViewTo(child(), target_bounds);
- animator()->SetAnimationDelegate(child(),
- std::make_unique<TestAnimationDelegate>());
- // The animator should be animating now.
- EXPECT_TRUE(animator()->IsAnimating());
- EXPECT_TRUE(animator()->IsAnimating(child()));
- // Run the message loop; the delegate exits the loop when the animation is
- // done.
- if (use_long_duration)
- task_environment_.FastForwardBy(animation_duration);
- base::RunLoop().Run();
- // Make sure the bounds match of the view that was animated match and the
- // layer is destroyed.
- EXPECT_EQ(target_bounds, child()->bounds());
- EXPECT_FALSE(child()->layer());
- // |child| shouldn't be animating anymore.
- EXPECT_FALSE(animator()->IsAnimating(child()));
- return child()->repaint_count();
- }
- base::test::SingleThreadTaskEnvironment task_environment_;
- private:
- TestView parent_;
- // TODO(crbug.com/1298696): views_unittests breaks with MTECheckedPtr
- // enabled. Triage.
- raw_ptr<TestView, DegradeToNoOpWhenMTE> child_; // Owned by |parent_|.
- std::unique_ptr<BoundsAnimator> animator_;
- };
- // Checks animate view to.
- TEST_F(BoundsAnimatorTest, AnimateViewTo) {
- gfx::Rect initial_bounds(0, 0, 10, 10);
- child()->SetBoundsRect(initial_bounds);
- gfx::Rect target_bounds(10, 10, 20, 20);
- animator()->AnimateViewTo(child(), target_bounds);
- animator()->SetAnimationDelegate(child(),
- std::make_unique<TestAnimationDelegate>());
- // The animator should be animating now.
- EXPECT_TRUE(animator()->IsAnimating());
- EXPECT_TRUE(animator()->IsAnimating(child()));
- // Run the message loop; the delegate exits the loop when the animation is
- // done.
- base::RunLoop().Run();
- // Make sure the bounds match of the view that was animated match.
- EXPECT_EQ(target_bounds, child()->bounds());
- // |child| shouldn't be animating anymore.
- EXPECT_FALSE(animator()->IsAnimating(child()));
- // The parent should have been told to repaint as the animation progressed.
- // The resulting rect is the union of the original and target bounds.
- EXPECT_EQ(gfx::UnionRects(target_bounds, initial_bounds),
- parent()->dirty_rect());
- }
- // Make sure that removing/deleting a child view while animating stops the
- // view's animation and will not result in a crash.
- TEST_F(BoundsAnimatorTest, DeleteWhileAnimating) {
- animator()->AnimateViewTo(child(), gfx::Rect(0, 0, 10, 10));
- animator()->SetAnimationDelegate(child(), std::make_unique<OwnedDelegate>());
- EXPECT_TRUE(animator()->IsAnimating(child()));
- // Make sure that animation is removed upon deletion.
- delete child();
- EXPECT_FALSE(animator()->GetAnimationForView(child()));
- EXPECT_FALSE(animator()->IsAnimating(child()));
- }
- // Make sure an AnimationDelegate is deleted when canceled.
- TEST_F(BoundsAnimatorTest, DeleteDelegateOnCancel) {
- animator()->AnimateViewTo(child(), gfx::Rect(0, 0, 10, 10));
- animator()->SetAnimationDelegate(child(), std::make_unique<OwnedDelegate>());
- animator()->Cancel();
- // The animator should no longer be animating.
- EXPECT_FALSE(animator()->IsAnimating());
- EXPECT_FALSE(animator()->IsAnimating(child()));
- // The cancel should both cancel the delegate and delete it.
- EXPECT_TRUE(OwnedDelegate::GetAndClearCanceled());
- EXPECT_TRUE(OwnedDelegate::GetAndClearDeleted());
- }
- // Make sure that the AnimationDelegate of the running animation is deleted when
- // a new animation is scheduled.
- TEST_F(BoundsAnimatorTest, DeleteDelegateOnNewAnimate) {
- const gfx::Rect target_bounds_first(0, 0, 10, 10);
- animator()->AnimateViewTo(child(), target_bounds_first);
- animator()->SetAnimationDelegate(child(), std::make_unique<OwnedDelegate>());
- // Start an animation on the same view with different target bounds.
- const gfx::Rect target_bounds_second(0, 5, 10, 10);
- animator()->AnimateViewTo(child(), target_bounds_second);
- // Starting a new animation should both cancel the delegate and delete it.
- EXPECT_TRUE(OwnedDelegate::GetAndClearDeleted());
- EXPECT_TRUE(OwnedDelegate::GetAndClearCanceled());
- }
- // Make sure that the duplicate animation request does not interrupt the running
- // animation.
- TEST_F(BoundsAnimatorTest, HandleDuplicateAnimation) {
- const gfx::Rect target_bounds(0, 0, 10, 10);
- animator()->AnimateViewTo(child(), target_bounds);
- animator()->SetAnimationDelegate(child(), std::make_unique<OwnedDelegate>());
- // Request the animation with the same view/target bounds.
- animator()->AnimateViewTo(child(), target_bounds);
- // Verify that the existing animation is not interrupted.
- EXPECT_FALSE(OwnedDelegate::GetAndClearDeleted());
- EXPECT_FALSE(OwnedDelegate::GetAndClearCanceled());
- }
- // Make sure that a duplicate animation request that specifies a different
- // delegate swaps out that delegate.
- TEST_F(BoundsAnimatorTest, DuplicateAnimationsCanReplaceDelegate) {
- const gfx::Rect target_bounds(0, 0, 10, 10);
- animator()->AnimateViewTo(child(), target_bounds);
- animator()->SetAnimationDelegate(child(), std::make_unique<OwnedDelegate>());
- // Request the animation with the same view/target bounds but a different
- // delegate.
- animator()->AnimateViewTo(child(), target_bounds,
- std::make_unique<OwnedDelegate>());
- // Verify that the delegate was replaced.
- EXPECT_TRUE(OwnedDelegate::GetAndClearDeleted());
- // The animation still should not have been canceled.
- EXPECT_FALSE(OwnedDelegate::GetAndClearCanceled());
- }
- // Makes sure StopAnimating works.
- TEST_F(BoundsAnimatorTest, StopAnimating) {
- std::unique_ptr<OwnedDelegate> delegate(std::make_unique<OwnedDelegate>());
- animator()->AnimateViewTo(child(), gfx::Rect(0, 0, 10, 10));
- animator()->SetAnimationDelegate(child(), std::make_unique<OwnedDelegate>());
- animator()->StopAnimatingView(child());
- // Shouldn't be animating now.
- EXPECT_FALSE(animator()->IsAnimating());
- EXPECT_FALSE(animator()->IsAnimating(child()));
- // Stopping should both cancel the delegate and delete it.
- EXPECT_TRUE(OwnedDelegate::GetAndClearDeleted());
- EXPECT_TRUE(OwnedDelegate::GetAndClearCanceled());
- }
- // Make sure Complete completes in-progress animations.
- TEST_F(BoundsAnimatorTest, CompleteAnimation) {
- std::unique_ptr<OwnedDelegate> delegate(std::make_unique<OwnedDelegate>());
- const gfx::Rect target_bounds = gfx::Rect(0, 0, 10, 10);
- animator()->AnimateViewTo(child(), target_bounds);
- animator()->SetAnimationDelegate(child(), std::make_unique<OwnedDelegate>());
- animator()->Complete();
- // Shouldn't be animating now.
- EXPECT_FALSE(animator()->IsAnimating());
- EXPECT_FALSE(animator()->IsAnimating(child()));
- // Child should have been moved to the animation's target.
- EXPECT_EQ(target_bounds, child()->bounds());
- // Completing should delete the delegate.
- EXPECT_TRUE(OwnedDelegate::GetAndClearDeleted());
- EXPECT_FALSE(OwnedDelegate::GetAndClearCanceled());
- }
- // Verify that transform is used when the animation target bounds have the
- // same size with the current bounds' meanwhile having the transform option
- // enabled.
- TEST_F(BoundsAnimatorTest, UseTransformsAnimateViewTo) {
- RecreateAnimator(/*use_transforms=*/true);
- const gfx::Rect initial_bounds(0, 0, 10, 10);
- child()->SetBoundsRect(initial_bounds);
- // Ensure that the target bounds have the same size with the initial bounds'
- // to apply transform to bounds animation.
- const gfx::Rect target_bounds_without_resize(gfx::Point(10, 10),
- initial_bounds.size());
- const int repaint_time_from_short_animation =
- GetRepaintTimeFromBoundsAnimation(target_bounds_without_resize,
- /*use_long_duration=*/false);
- const int repaint_time_from_long_animation =
- GetRepaintTimeFromBoundsAnimation(initial_bounds,
- /*use_long_duration=*/true);
- // The number of repaints in long animation should be the same as with the
- // short animation.
- EXPECT_EQ(repaint_time_from_short_animation,
- repaint_time_from_long_animation);
- }
- // Verify that transform is not used when the animation target bounds have the
- // different size from the current bounds' even if transform is preferred.
- TEST_F(BoundsAnimatorTest, NoTransformForScalingAnimation) {
- RecreateAnimator(/*use_transforms=*/true);
- const gfx::Rect initial_bounds(0, 0, 10, 10);
- child()->SetBoundsRect(initial_bounds);
- // Ensure that the target bounds have the different size with the initial
- // bounds' to repaint bounds in each animation tick.
- const gfx::Rect target_bounds_with_reize(gfx::Point(10, 10),
- gfx::Size(20, 20));
- const int repaint_time_from_short_animation =
- GetRepaintTimeFromBoundsAnimation(target_bounds_with_reize,
- /*use_long_duration=*/false);
- const int repaint_time_from_long_animation =
- GetRepaintTimeFromBoundsAnimation(initial_bounds,
- /*use_long_duration=*/true);
- // When creating bounds animation with repaint, the longer bounds animation
- // should have more repaint counts.
- EXPECT_GT(repaint_time_from_long_animation,
- repaint_time_from_short_animation);
- }
- // Tests that the transforms option does not crash when a view's bounds start
- // off empty.
- TEST_F(BoundsAnimatorTest, UseTransformsAnimateViewToEmptySrc) {
- RecreateAnimator(/*use_transforms=*/true);
- gfx::Rect initial_bounds(0, 0, 0, 0);
- child()->SetBoundsRect(initial_bounds);
- gfx::Rect target_bounds(10, 10, 20, 20);
- child()->set_repaint_count(0);
- animator()->AnimateViewTo(child(), target_bounds);
- animator()->SetAnimationDelegate(child(),
- std::make_unique<TestAnimationDelegate>());
- // The animator should be animating now.
- EXPECT_TRUE(animator()->IsAnimating());
- EXPECT_TRUE(animator()->IsAnimating(child()));
- // Run the message loop; the delegate exits the loop when the animation is
- // done.
- base::RunLoop().Run();
- EXPECT_EQ(target_bounds, child()->bounds());
- }
- // Tests that when using the transform option on the bounds animator, cancelling
- // the animation part way results in the correct bounds applied.
- TEST_F(BoundsAnimatorTest, UseTransformsCancelAnimation) {
- RecreateAnimator(/*use_transforms=*/true);
- // Ensure that |initial_bounds| has the same size with |target_bounds| to
- // create bounds animation via the transform.
- const gfx::Rect initial_bounds(0, 0, 10, 10);
- const gfx::Rect target_bounds(10, 10, 10, 10);
- child()->SetBoundsRect(initial_bounds);
- const base::TimeDelta duration = base::Milliseconds(200);
- animator()->SetAnimationDuration(duration);
- // Use a linear tween so we can estimate the expected bounds.
- animator()->set_tween_type(gfx::Tween::LINEAR);
- animator()->AnimateViewTo(child(), target_bounds);
- animator()->SetAnimationDelegate(child(),
- std::make_unique<TestAnimationDelegate>());
- EXPECT_TRUE(animator()->IsAnimating());
- EXPECT_TRUE(animator()->IsAnimating(child()));
- // Stop halfway and cancel. The child should have its bounds updated to
- // exactly halfway between |initial_bounds| and |target_bounds|.
- const gfx::Rect expected_bounds(5, 5, 10, 10);
- task_environment_.FastForwardBy(base::Milliseconds(100));
- EXPECT_EQ(initial_bounds, child()->bounds());
- animator()->Cancel();
- EXPECT_EQ(expected_bounds, child()->bounds());
- }
- // Test that when using the transform option on the bounds animator, cancelling
- // the animation part way under RTL results in the correct bounds applied.
- TEST_F(BoundsAnimatorTest, UseTransformsCancelAnimationRTL) {
- // Enable RTL.
- base::test::ScopedRestoreICUDefaultLocale scoped_locale("he");
- RecreateAnimator(/*use_transforms=*/true);
- // Ensure that |initial_bounds| has the same size with |target_bounds| to
- // create bounds animation via the transform.
- const gfx::Rect initial_bounds(0, 0, 10, 10);
- const gfx::Rect target_bounds(10, 10, 10, 10);
- child()->SetBoundsRect(initial_bounds);
- const base::TimeDelta duration = base::Milliseconds(200);
- animator()->SetAnimationDuration(duration);
- // Use a linear tween so we can estimate the expected bounds.
- animator()->set_tween_type(gfx::Tween::LINEAR);
- animator()->AnimateViewTo(child(), target_bounds);
- EXPECT_TRUE(animator()->IsAnimating());
- EXPECT_TRUE(animator()->IsAnimating(child()));
- // Stop halfway and cancel. The child should have its bounds updated to
- // exactly halfway between |initial_bounds| and |target_bounds|.
- const gfx::Rect expected_bounds(5, 5, 10, 10);
- task_environment_.FastForwardBy(base::Milliseconds(100));
- EXPECT_EQ(initial_bounds, child()->bounds());
- animator()->Cancel();
- EXPECT_EQ(expected_bounds, child()->bounds());
- }
- // Verify that the bounds animation which updates the transform of views work
- // as expected under RTL (https://crbug.com/1067033).
- TEST_F(BoundsAnimatorTest, VerifyBoundsAnimatorUnderRTL) {
- // Enable RTL.
- base::test::ScopedRestoreICUDefaultLocale scoped_locale("he");
- RecreateAnimator(/*use_transforms=*/true);
- parent()->SetBounds(0, 0, 40, 40);
- const gfx::Rect initial_bounds(0, 0, 10, 10);
- child()->SetBoundsRect(initial_bounds);
- const gfx::Rect target_bounds(10, 10, 10, 10);
- const base::TimeDelta animation_duration = base::Milliseconds(10);
- animator()->SetAnimationDuration(animation_duration);
- child()->set_repaint_count(0);
- animator()->AnimateViewTo(child(), target_bounds);
- base::RunLoop run_loop;
- animator()->SetAnimationDelegate(
- child(),
- std::make_unique<RTLAnimationTestDelegate>(
- initial_bounds, target_bounds, child(), run_loop.QuitClosure()));
- // The animator should be animating now.
- EXPECT_TRUE(animator()->IsAnimating());
- EXPECT_TRUE(animator()->IsAnimating(child()));
- run_loop.Run();
- EXPECT_FALSE(animator()->IsAnimating(child()));
- }
- } // namespace views
|