| // 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 |