blob: 2295d937f3828e9c7a0ffc2ae3102d3bc99ff802 [file] [log] [blame]
// Copyright 2014 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/compositor_extra/shadow.h"
#include "base/test/test_discardable_memory_allocator.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/shadow_util.h"
#include "ui/gfx/shadow_value.h"
namespace ui {
namespace {
constexpr int kElevationLarge = 24;
constexpr int kElevationSmall = 6;
// A specific elevation used for testing EvictUniquelyOwnedDetail.
constexpr int kElevationUnique = 66;
gfx::Insets InsetsForElevation(int elevation) {
return -gfx::Insets(2 * elevation) +
gfx::Insets::TLBR(elevation, 0, -elevation, 0);
}
gfx::Size NineboxImageSizeForElevationAndCornerRadius(int elevation,
int corner_radius) {
auto values = gfx::ShadowValue::MakeMdShadowValues(elevation);
gfx::Rect bounds(0, 0, 1, 1);
bounds.Inset(-gfx::ShadowValue::GetBlurRegion(values));
bounds.Inset(-gfx::Insets(corner_radius));
return bounds.size();
}
// Calculates the minimum shadow content size for given elevation and corner
// radius.
gfx::Size MinContentSizeForElevationAndCornerRadius(int elevation,
int corner_radius) {
const int dimension = 4 * elevation + 2 * corner_radius;
return gfx::Size(dimension, dimension);
}
class ShadowTest : public testing::Test {
public:
ShadowTest(const ShadowTest&) = delete;
ShadowTest& operator=(const ShadowTest&) = delete;
protected:
ShadowTest() {}
~ShadowTest() override {}
void SetUp() override {
base::DiscardableMemoryAllocator::SetInstance(
&discardable_memory_allocator_);
}
void TearDown() override {
base::DiscardableMemoryAllocator::SetInstance(nullptr);
}
private:
base::TestDiscardableMemoryAllocator discardable_memory_allocator_;
};
// Test if the proper content bounds is calculated based on the current style.
TEST_F(ShadowTest, SetContentBounds) {
ScopedAnimationDurationScaleMode zero_duration_mode(
ScopedAnimationDurationScaleMode::ZERO_DURATION);
// Verify that layer bounds are outset from content bounds.
Shadow shadow;
{
shadow.Init(kElevationLarge);
gfx::Rect content_bounds(100, 100, 300, 300);
shadow.SetContentBounds(content_bounds);
EXPECT_EQ(content_bounds, shadow.content_bounds());
gfx::Rect shadow_bounds(content_bounds);
shadow_bounds.Inset(InsetsForElevation(kElevationLarge));
EXPECT_EQ(shadow_bounds, shadow.layer()->bounds());
}
{
shadow.SetElevation(kElevationSmall);
gfx::Rect content_bounds(100, 100, 300, 300);
shadow.SetContentBounds(content_bounds);
EXPECT_EQ(content_bounds, shadow.content_bounds());
gfx::Rect shadow_bounds(content_bounds);
shadow_bounds.Inset(InsetsForElevation(kElevationSmall));
EXPECT_EQ(shadow_bounds, shadow.layer()->bounds());
}
}
// Test that the elevation is reduced when the contents are too small to handle
// the full elevation.
TEST_F(ShadowTest, AdjustElevationForSmallContents) {
Shadow shadow;
shadow.Init(kElevationLarge);
{
gfx::Rect content_bounds(100, 100, 300, 300);
shadow.SetContentBounds(content_bounds);
gfx::Rect shadow_bounds(content_bounds);
shadow_bounds.Inset(InsetsForElevation(kElevationLarge));
EXPECT_EQ(shadow_bounds, shadow.layer()->bounds());
}
{
constexpr int kWidth = 80;
gfx::Rect content_bounds(100, 100, kWidth, 300);
shadow.SetContentBounds(content_bounds);
gfx::Rect shadow_bounds(content_bounds);
shadow_bounds.Inset(InsetsForElevation((kWidth - 4) / 4));
EXPECT_EQ(shadow_bounds, shadow.layer()->bounds());
}
{
constexpr int kHeight = 80;
gfx::Rect content_bounds(100, 100, 300, kHeight);
shadow.SetContentBounds(content_bounds);
gfx::Rect shadow_bounds(content_bounds);
shadow_bounds.Inset(InsetsForElevation((kHeight - 4) / 4));
EXPECT_EQ(shadow_bounds, shadow.layer()->bounds());
}
}
// Test that rounded corner radius is handled correctly.
TEST_F(ShadowTest, AdjustRoundedCornerRadius) {
Shadow shadow;
shadow.Init(kElevationSmall);
gfx::Rect content_bounds(100, 100, 300, 300);
shadow.SetContentBounds(content_bounds);
EXPECT_EQ(content_bounds, shadow.content_bounds());
shadow.SetRoundedCornerRadius(0);
gfx::Rect shadow_bounds(content_bounds);
shadow_bounds.Inset(InsetsForElevation(kElevationSmall));
EXPECT_EQ(shadow_bounds, shadow.layer()->bounds());
EXPECT_EQ(NineboxImageSizeForElevationAndCornerRadius(6, 0),
shadow.details_for_testing()->ninebox_image.size());
}
// Test that the uniquely owned shadow image is evicted from the cache when new
// shadow details are created.
TEST_F(ShadowTest, EvictUniquelyOwnedDetail) {
// Insert a new shadow with unique details which will evict existing details
// from the cache.
{
Shadow shadow_new;
shadow_new.Init(kElevationUnique);
shadow_new.SetRoundedCornerRadius(2);
const gfx::Size min_content_size =
MinContentSizeForElevationAndCornerRadius(kElevationUnique, 2);
shadow_new.SetContentBounds(gfx::Rect(min_content_size));
// The cache size should be 1.
EXPECT_EQ(1u, gfx::ShadowDetails::GetDetailsCacheSizeForTest());
// Creating a shadow with the same detail won't increase the cache size.
Shadow shadow_same;
shadow_same.Init(kElevationUnique);
shadow_same.SetRoundedCornerRadius(2);
shadow_same.SetContentBounds(
gfx::Rect(gfx::Point(10, 10), min_content_size + gfx::Size(50, 50)));
// The cache size is unchanged.
EXPECT_EQ(1u, gfx::ShadowDetails::GetDetailsCacheSizeForTest());
// Creating a new uniquely owned detail will increase the cache size.
gfx::ShadowDetails::Get(kElevationUnique, 3);
EXPECT_EQ(2u, gfx::ShadowDetails::GetDetailsCacheSizeForTest());
// Creating a shadow with different details will replace the uniquely owned
// detail.
Shadow shadow_small;
shadow_small.Init(kElevationSmall);
shadow_small.SetRoundedCornerRadius(2);
shadow_small.SetContentBounds(gfx::Rect(
MinContentSizeForElevationAndCornerRadius(kElevationSmall, 2)));
EXPECT_EQ(2u, gfx::ShadowDetails::GetDetailsCacheSizeForTest());
// Changing the shadow appearance will insert a new detail in the cache and
// make the old detail uniquely owned.
shadow_small.SetRoundedCornerRadius(3);
EXPECT_EQ(3u, gfx::ShadowDetails::GetDetailsCacheSizeForTest());
// Changing the shadow with another appearance will replace the uniquely
// owned detail.
shadow_small.SetRoundedCornerRadius(4);
EXPECT_EQ(3u, gfx::ShadowDetails::GetDetailsCacheSizeForTest());
}
// After destroying the all the shadows, the cache has 3 uniquely owned
// details.
EXPECT_EQ(3u, gfx::ShadowDetails::GetDetailsCacheSizeForTest());
// After inserting a new detail, the uniquely owned details will be evicted.
Shadow shadow_large;
shadow_large.Init(kElevationLarge);
shadow_large.SetRoundedCornerRadius(2);
shadow_large.SetContentBounds(
gfx::Rect(MinContentSizeForElevationAndCornerRadius(kElevationLarge, 2)));
// The cache size is unchanged.
EXPECT_EQ(1u, gfx::ShadowDetails::GetDetailsCacheSizeForTest());
}
} // namespace
} // namespace ui