blob: 9f9c8274180f220f998d487935f5683ffb27163c [file] [log] [blame]
// Copyright 2022 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/controls/animated_image_view.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "cc/paint/display_item_list.h"
#include "cc/paint/paint_op_buffer_iterator.h"
#include "cc/test/skia_common.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/paint_context.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/lottie/animation.h"
#include "ui/views/paint_info.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace {
using ::testing::FloatEq;
using ::testing::NotNull;
template <typename T>
const T* FindPaintOp(const cc::PaintRecord& paint_record,
cc::PaintOpType paint_op_type) {
for (const cc::PaintOp& op : paint_record) {
if (op.GetType() == paint_op_type)
return static_cast<const T*>(&op);
if (op.GetType() == cc::PaintOpType::DrawRecord) {
const T* record_op_result = FindPaintOp<T>(
static_cast<const cc::DrawRecordOp&>(op).record, paint_op_type);
if (record_op_result)
return static_cast<const T*>(record_op_result);
}
}
return nullptr;
}
class AnimatedImageViewTest : public ViewsTestBase {
protected:
static constexpr int kDefaultWidthAndHeight = 100;
static constexpr gfx::Size kDefaultSize =
gfx::Size(kDefaultWidthAndHeight, kDefaultWidthAndHeight);
void SetUp() override {
ViewsTestBase::SetUp();
Widget::InitParams params =
CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = gfx::Rect(kDefaultSize);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget_.Init(std::move(params));
view_ = widget_.SetContentsView(std::make_unique<AnimatedImageView>());
view_->SetUseDefaultFillLayout(true);
widget_.Show();
}
void TearDown() override {
widget_.Close();
ViewsTestBase::TearDown();
}
std::unique_ptr<lottie::Animation> CreateAnimationWithSize(
const gfx::Size& size) {
return std::make_unique<lottie::Animation>(
cc::CreateSkottie(size, /*duration_secs=*/1));
}
cc::PaintRecord Paint(const gfx::Rect& invalidation_rect) {
auto display_list = base::MakeRefCounted<cc::DisplayItemList>();
ui::PaintContext paint_context(display_list.get(),
/*device_scale_factor=*/1.f,
invalidation_rect, /*is_pixel_canvas=*/true);
view_->Paint(PaintInfo::CreateRootPaintInfo(paint_context,
invalidation_rect.size()));
RunPendingMessages();
return display_list->FinalizeAndReleaseAsRecord();
}
Widget widget_;
raw_ptr<AnimatedImageView> view_;
};
TEST_F(AnimatedImageViewTest, PaintsWithAdditionalTranslation) {
view_->SetAnimatedImage(CreateAnimationWithSize(gfx::Size(80, 80)));
view_->SetVerticalAlignment(ImageViewBase::Alignment::kCenter);
view_->SetHorizontalAlignment(ImageViewBase::Alignment::kCenter);
views::test::RunScheduledLayout(view_);
view_->Play();
static constexpr float kExpectedDefaultOrigin =
(kDefaultWidthAndHeight - 80) / 2;
// Default should be no extra translation.
cc::PaintRecord paint_record = Paint(view_->bounds());
const cc::TranslateOp* translate_op =
FindPaintOp<cc::TranslateOp>(paint_record, cc::PaintOpType::Translate);
ASSERT_THAT(translate_op, NotNull());
EXPECT_THAT(translate_op->dx, FloatEq(kExpectedDefaultOrigin));
EXPECT_THAT(translate_op->dy, FloatEq(kExpectedDefaultOrigin));
view_->SetAdditionalTranslation(gfx::Vector2d(5, 5));
paint_record = Paint(view_->bounds());
translate_op =
FindPaintOp<cc::TranslateOp>(paint_record, cc::PaintOpType::Translate);
ASSERT_THAT(translate_op, NotNull());
EXPECT_THAT(translate_op->dx, FloatEq(kExpectedDefaultOrigin + 5));
EXPECT_THAT(translate_op->dy, FloatEq(kExpectedDefaultOrigin + 5));
view_->SetAdditionalTranslation(gfx::Vector2d(5, -5));
paint_record = Paint(view_->bounds());
translate_op =
FindPaintOp<cc::TranslateOp>(paint_record, cc::PaintOpType::Translate);
ASSERT_THAT(translate_op, NotNull());
EXPECT_THAT(translate_op->dx, FloatEq(kExpectedDefaultOrigin + 5));
EXPECT_THAT(translate_op->dy, FloatEq(kExpectedDefaultOrigin - 5));
view_->SetAdditionalTranslation(gfx::Vector2d(-5, 5));
paint_record = Paint(view_->bounds());
translate_op =
FindPaintOp<cc::TranslateOp>(paint_record, cc::PaintOpType::Translate);
ASSERT_THAT(translate_op, NotNull());
EXPECT_THAT(translate_op->dx, FloatEq(kExpectedDefaultOrigin - 5));
EXPECT_THAT(translate_op->dy, FloatEq(kExpectedDefaultOrigin + 5));
view_->SetAdditionalTranslation(gfx::Vector2d(-5, -5));
paint_record = Paint(view_->bounds());
translate_op =
FindPaintOp<cc::TranslateOp>(paint_record, cc::PaintOpType::Translate);
ASSERT_THAT(translate_op, NotNull());
EXPECT_THAT(translate_op->dx, FloatEq(kExpectedDefaultOrigin - 5));
EXPECT_THAT(translate_op->dy, FloatEq(kExpectedDefaultOrigin - 5));
}
TEST_F(AnimatedImageViewTest, PlayBeforeWidget) {
auto animated_view = std::make_unique<AnimatedImageView>();
animated_view->SetAnimatedImage(CreateAnimationWithSize(gfx::Size(80, 80)));
// It should be valid to call `Play` before `animated_view` has been added to
// a widget.
animated_view->Play();
view_ = widget_.SetContentsView(std::move(animated_view));
view_->SetUseDefaultFillLayout(true);
widget_.Show();
}
} // namespace
} // namespace views