// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/scoped_layer_tree_synchronizer.h"
#include <cmath>
#include <memory>
#include <tuple>
#include <utility>
#include "ash/test/ash_test_base.h"
#include "base/functional/callback_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/test/test_windows.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/rrect_f.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
constexpr gfx::Rect kRootBounds(0, 0, 50, 50);
constexpr gfx::RoundedCornersF kRootLayerRadii(10);
constexpr gfx::RoundedCornersF kZeroRadii(0);
std::unique_ptr<ui::Layer> CreateLayer() {
return std::make_unique<ui::Layer>();
}
using BoundsWithRoundedCorners = std::pair<gfx::Rect, gfx::RoundedCornersF>;
using UpdatingScopedLayerTreeSynchronizerTestParam =
std::tuple</*root_layer_bounds=*/BoundsWithRoundedCorners,
/*child_layer_bounds=*/BoundsWithRoundedCorners,
/*expected_child_layer_radii=*/gfx::RoundedCornersF>;
class UpdatingScopedLayerTreeSynchronizerTest
: public testing::TestWithParam<
UpdatingScopedLayerTreeSynchronizerTestParam> {
public:
UpdatingScopedLayerTreeSynchronizerTest() = default;
UpdatingScopedLayerTreeSynchronizerTest(
const UpdatingScopedLayerTreeSynchronizerTest&) = delete;
UpdatingScopedLayerTreeSynchronizerTest& operator=(
const UpdatingScopedLayerTreeSynchronizerTest&) = delete;
~UpdatingScopedLayerTreeSynchronizerTest() override = default;
protected:
gfx::Rect root_layer_bounds() const { return std::get<0>(GetParam()).first; }
gfx::RoundedCornersF root_layer_radii() const {
return std::get<0>(GetParam()).second;
}
gfx::Rect child_layer_bounds() const { return std::get<1>(GetParam()).first; }
gfx::RoundedCornersF child_layer_radii() const {
return std::get<1>(GetParam()).second;
}
gfx::RoundedCornersF expected_child_layer_radii() const {
return std::get<2>(GetParam());
}
};
TEST_P(UpdatingScopedLayerTreeSynchronizerTest, OverlappingCorners) {
// Layer Tree:
// +root
// +--child_layer
std::unique_ptr<ui::Layer> root = CreateLayer();
std::unique_ptr<ui::Layer> child_layer = CreateLayer();
// Set up layer tree.
root->Add(child_layer.get());
// Set up layer bounds.
root->SetBounds(root_layer_bounds());
child_layer->SetBounds(child_layer_bounds());
// Set layer properties.
root->SetRoundedCornerRadius(root_layer_radii());
root->SetIsFastRoundedCorner(true);
child_layer->SetRoundedCornerRadius(child_layer_radii());
child_layer->SetIsFastRoundedCorner(true);
auto layer_tree_synchronizer = std::make_unique<ScopedLayerTreeSynchronizer>(
root.get(), /*restore_tree=*/false);
ASSERT_EQ(child_layer->rounded_corner_radii(), child_layer_radii());
layer_tree_synchronizer->SynchronizeRoundedCorners(
root.get(),
gfx::RRectF(gfx::RectF(root_layer_bounds()), root_layer_radii()));
// Root layer bounds and radii should be unaffected.
ASSERT_EQ(root->rounded_corner_radii(), root_layer_radii());
ASSERT_EQ(root->bounds(), root_layer_bounds());
EXPECT_EQ(child_layer->rounded_corner_radii(), expected_child_layer_radii());
}
UpdatingScopedLayerTreeSynchronizerTestParam MakeTestParams(
const gfx::Rect& root_layer_bounds,
const gfx::RoundedCornersF& root_layer_radii,
const gfx::Rect& child_layer_bounds,
const gfx::RoundedCornersF& child_layer_radii,
const gfx::RoundedCornersF& expected_child_layer_radii) {
return std::make_tuple(std::make_pair(root_layer_bounds, root_layer_radii),
std::make_pair(child_layer_bounds, child_layer_radii),
expected_child_layer_radii);
}
INSTANTIATE_TEST_SUITE_P(
SanityTests,
UpdatingScopedLayerTreeSynchronizerTest,
testing::Values(
// Child layer has zero bounds.
MakeTestParams(kRootBounds,
kZeroRadii,
gfx::Rect(),
kZeroRadii,
kZeroRadii),
// Parent layer has zero bounds.
MakeTestParams(gfx::Rect(),
kZeroRadii,
gfx::Rect(5, 5, 10, 10),
kZeroRadii,
kZeroRadii),
// If child layer has no rounded corners, we do not update those layers.
MakeTestParams(kRootBounds,
gfx::RoundedCornersF(5),
gfx::Rect(0, 0, 50, 50),
kZeroRadii,
kZeroRadii)));
INSTANTIATE_TEST_SUITE_P(
RootLayerHasNoRoundedCorners,
UpdatingScopedLayerTreeSynchronizerTest,
testing::Values(
// Child layer is the same size as the root layer.
MakeTestParams(kRootBounds,
kZeroRadii,
gfx::Rect(0, 0, 50, 50),
kZeroRadii,
kZeroRadii),
// Child layer (has rounded corners) is the same size as the root layer.
MakeTestParams(kRootBounds,
kZeroRadii,
gfx::Rect(0, 0, 50, 50),
gfx::RoundedCornersF(5),
gfx::RoundedCornersF(5)),
// Child layer (has rounded corners) fits inside the root layer.
MakeTestParams(kRootBounds,
kZeroRadii,
gfx::Rect(10, 10, 20, 0),
gfx::RoundedCornersF(5),
gfx::RoundedCornersF(5))));
INSTANTIATE_TEST_SUITE_P(
RootLayerAndChildLayerHaveRoundedCorners,
UpdatingScopedLayerTreeSynchronizerTest,
testing::Values(
// Root and child layer overlaps completely.
MakeTestParams(kRootBounds,
gfx::RoundedCornersF(10),
gfx::Rect(0, 0, 50, 50),
gfx::RoundedCornersF(10),
gfx::RoundedCornersF(10)),
// Child layer fits inside the root.
MakeTestParams(kRootBounds,
gfx::RoundedCornersF(10),
gfx::Rect(5, 5, 10, 10),
gfx::RoundedCornersF(2),
gfx::RoundedCornersF(2)),
// Corners do not intersect but child layer corners
// are outside root layer rounded bounds. (Test 1)
MakeTestParams(kRootBounds,
gfx::RoundedCornersF(10),
gfx::Rect(0, 0, 50, 50),
gfx::RoundedCornersF(9),
gfx::RoundedCornersF(10)),
// Corners do not intersect but child layer corners
// are outside root layer rounded bounds. (Test 2)
MakeTestParams(kRootBounds,
gfx::RoundedCornersF(20),
gfx::Rect(5, 0, 40, 50),
gfx::RoundedCornersF(5),
gfx::RoundedCornersF(20)),
// Corners intersect. (Test 1)
MakeTestParams(kRootBounds,
gfx::RoundedCornersF(20),
gfx::Rect(3, 5, 44, 40),
gfx::RoundedCornersF(5),
gfx::RoundedCornersF(20)),
// Corners partially intersect.
MakeTestParams(kRootBounds,
gfx::RoundedCornersF(20),
gfx::Rect(4, 5, 43, 40),
gfx::RoundedCornersF(5),
gfx::RoundedCornersF(5, 20, 20, 5))));
class ScopedLayerTreeSynchronizerTest : public testing::TestWithParam<bool> {
public:
ScopedLayerTreeSynchronizerTest() = default;
ScopedLayerTreeSynchronizerTest(const ScopedLayerTreeSynchronizerTest&) =
delete;
ScopedLayerTreeSynchronizerTest& operator=(
const ScopedLayerTreeSynchronizerTest&) = delete;
~ScopedLayerTreeSynchronizerTest() override = default;
protected:
bool restore_layer_tree() const { return GetParam(); }
};
TEST_P(ScopedLayerTreeSynchronizerTest, UpdatingLayerTree) {
// Layer Tree:
// +root (has rounded corners)
// +--layer1 (has intersecting rounded corners with root)
// +----layer2 (has intersecting rounded corners with root)
// +--layer3 (has rounded corners)
constexpr gfx::Rect kLayer1Bounds(0, 0, 30, 30);
constexpr gfx::Rect kLayer2Bounds(20, 20, 60, 60);
constexpr gfx::Rect kLayer3Bounds(10, 10, 20, 20);
constexpr gfx::RoundedCornersF kLayer1Radii(5);
constexpr gfx::RoundedCornersF kLayer2Radii(7);
constexpr gfx::RoundedCornersF kLayer3Radii(0);
constexpr float kLayer2Scale = 0.5;
std::unique_ptr<ui::Layer> root = CreateLayer();
std::unique_ptr<ui::Layer> layer_1 = CreateLayer();
std::unique_ptr<ui::Layer> layer_2 = CreateLayer();
std::unique_ptr<ui::Layer> layer_3 = CreateLayer();
// Set up layer tree.
root->Add(layer_1.get());
root->Add(layer_3.get());
layer_1->Add(layer_2.get());
// Set up layer bounds.
root->SetBounds(kRootBounds);
layer_1->SetBounds(kLayer1Bounds);
layer_2->SetBounds(kLayer2Bounds);
layer_3->SetBounds(kLayer3Bounds);
// Set layer transform.
gfx::Transform layer_2_transform;
layer_2_transform.Scale(kLayer2Scale);
layer_2->SetTransform(layer_2_transform);
// Set layer properties.
root->SetRoundedCornerRadius(kRootLayerRadii);
root->SetIsFastRoundedCorner(true);
layer_1->SetRoundedCornerRadius(kLayer1Radii);
layer_1->SetIsFastRoundedCorner(true);
layer_2->SetRoundedCornerRadius(kLayer2Radii);
layer_2->SetIsFastRoundedCorner(true);
layer_3->SetRoundedCornerRadius(kLayer3Radii);
layer_3->SetIsFastRoundedCorner(true);
auto layer_tree_synchronizer = std::make_unique<ScopedLayerTreeSynchronizer>(
root.get(), restore_layer_tree());
layer_tree_synchronizer->SynchronizeRoundedCorners(
root.get(), gfx::RRectF(gfx::RectF(kRootBounds), kRootLayerRadii));
EXPECT_EQ(root->rounded_corner_radii(), kRootLayerRadii);
constexpr gfx::RoundedCornersF kUpdatedLayer1Radii =
gfx::RoundedCornersF(10, 5, 5, 5);
EXPECT_EQ(layer_1->rounded_corner_radii(), kUpdatedLayer1Radii);
constexpr gfx::RoundedCornersF kUpdatedLayer2Radii =
gfx::RoundedCornersF(7, 7, 20, 7);
EXPECT_EQ(layer_2->rounded_corner_radii(), kUpdatedLayer2Radii);
EXPECT_EQ(layer_3->rounded_corner_radii(), kLayer3Radii);
layer_tree_synchronizer->Restore();
EXPECT_EQ(root->rounded_corner_radii(), kRootLayerRadii);
EXPECT_EQ(layer_1->rounded_corner_radii(),
restore_layer_tree() ? kLayer1Radii : kUpdatedLayer1Radii);
EXPECT_EQ(layer_2->rounded_corner_radii(),
restore_layer_tree() ? kLayer2Radii : kUpdatedLayer2Radii);
EXPECT_EQ(layer_3->rounded_corner_radii(), kLayer3Radii);
}
INSTANTIATE_TEST_SUITE_P(/* no prefix */,
ScopedLayerTreeSynchronizerTest,
testing::Bool());
using ScopedWindowTreeSynchronizerTest = AshTestBase;
TEST_F(ScopedWindowTreeSynchronizerTest, Basics) {
// root
// +---transient_parent
// +------child_window
// +---transient_window_1
// +---transient_window_2
std::unique_ptr<aura::Window> root(
aura::test::CreateTestWindowWithId(0, nullptr));
std::unique_ptr<aura::Window> transient_parent(
aura::test::CreateTestWindowWithId(1, root.get()));
std::unique_ptr<aura::Window> child_window(
aura::test::CreateTestWindowWithId(2, transient_parent.get()));
std::unique_ptr<aura::Window> transient_window_1(
aura::test::CreateTestWindowWithId(3, root.get()));
std::unique_ptr<aura::Window> transient_window_2(
aura::test::CreateTestWindowWithId(4, root.get()));
wm::AddTransientChild(transient_parent.get(), transient_window_1.get());
wm::AddTransientChild(transient_parent.get(), transient_window_2.get());
transient_parent->SetBounds(gfx::Rect(0, 0, 1000, 500));
child_window->SetBounds(gfx::Rect(0, 0, 50, 50));
transient_window_1->SetBounds(gfx::Rect(0, 0, 1000, 500));
transient_window_2->SetBounds(gfx::Rect(20, 20, 200, 200));
transient_parent->layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(5));
child_window->layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(2));
transient_window_1->layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(5));
transient_window_2->layer()->SetRoundedCornerRadius(gfx::RoundedCornersF());
auto window_tree_synchronizer =
std::make_unique<ScopedWindowTreeSynchronizer>(root.get(),
/*restore_tree=*/true);
gfx::RRectF reference_bounds(gfx::RectF(0, 0, 1000, 500),
gfx::RoundedCornersF(10));
window_tree_synchronizer->SynchronizeRoundedCorners(
transient_parent.get(), /*consider_curvature=*/true, reference_bounds,
base::NullCallback());
// All the windows rooted at `transient_parent`(including transient windows)
// should be synchronized again `reference_bounds`.
EXPECT_EQ(transient_parent->layer()->rounded_corner_radii(),
gfx::RoundedCornersF(10));
EXPECT_EQ(child_window->layer()->rounded_corner_radii(),
gfx::RoundedCornersF(10, 2, 2, 2));
EXPECT_EQ(transient_window_1->layer()->rounded_corner_radii(),
gfx::RoundedCornersF(10));
EXPECT_EQ(transient_window_2->layer()->rounded_corner_radii(),
gfx::RoundedCornersF());
gfx::RRectF updated_reference_bounds(gfx::RectF(0, 0, 1000, 500),
gfx::RoundedCornersF(15));
window_tree_synchronizer->SynchronizeRoundedCorners(
transient_parent.get(), /*consider_curvature=*/true,
updated_reference_bounds, base::NullCallback());
// All the windows rooted at `transient_parent` should now have new rounded
// corners based on `updated_reference_bounds`.
EXPECT_EQ(transient_parent->layer()->rounded_corner_radii(),
gfx::RoundedCornersF(15));
EXPECT_EQ(child_window->layer()->rounded_corner_radii(),
gfx::RoundedCornersF(15, 2, 2, 2));
EXPECT_EQ(transient_window_1->layer()->rounded_corner_radii(),
gfx::RoundedCornersF(15));
EXPECT_EQ(transient_window_2->layer()->rounded_corner_radii(),
gfx::RoundedCornersF());
window_tree_synchronizer->Restore();
// All the windows should now be restored to their original state. (i.e
// before calling SynchronizeRoundedCorners() for the first time)
EXPECT_EQ(transient_parent->layer()->rounded_corner_radii(),
gfx::RoundedCornersF(5));
EXPECT_EQ(child_window->layer()->rounded_corner_radii(),
gfx::RoundedCornersF(2));
EXPECT_EQ(transient_window_1->layer()->rounded_corner_radii(),
gfx::RoundedCornersF(5));
EXPECT_EQ(transient_window_2->layer()->rounded_corner_radii(),
gfx::RoundedCornersF());
}
} // namespace
} // namespace ash