blob: 28a1cdf23525bd20db7fdc3979b44eae598fc368 [file] [log] [blame]
// Copyright 2019 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/layout/layout_manager_base.h"
#include <algorithm>
#include "base/memory/raw_ptr.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/test/test_views.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
namespace views {
namespace {
constexpr int kChildViewPadding = 5;
constexpr gfx::Size kMinimumSize(40, 50);
constexpr gfx::Size kPreferredSize(100, 90);
constexpr gfx::Size kSquarishSize(10, 11);
constexpr gfx::Size kLongSize(20, 8);
constexpr gfx::Size kTallSize(4, 22);
constexpr gfx::Size kLargeSize(30, 28);
// Dummy class that minimally implements LayoutManagerBase for basic
// functionality testing.
class TestLayoutManagerBase : public LayoutManagerBase {
public:
std::vector<const View*> GetIncludedChildViews() const {
std::vector<const View*> included;
std::ranges::copy_if(host_view()->children(), std::back_inserter(included),
[=, this](const View* child) {
return IsChildIncludedInLayout(child);
});
return included;
}
void OverrideProposedLayout(const ProposedLayout& forced_layout) {
forced_layout_ = forced_layout;
InvalidateHost(true);
}
// LayoutManagerBase:
ProposedLayout CalculateProposedLayout(
const SizeBounds& size_bounds) const override {
if (forced_layout_) {
return *forced_layout_;
}
ProposedLayout layout;
layout.host_size.set_width(std::clamp<SizeBound>(size_bounds.width(),
kMinimumSize.width(),
kPreferredSize.width())
.value());
layout.host_size.set_height(std::clamp<SizeBound>(size_bounds.height(),
kMinimumSize.height(),
kPreferredSize.height())
.value());
return layout;
}
void LayoutImpl() override {
++layout_count_;
LayoutManagerBase::LayoutImpl();
}
size_t layout_count() const { return layout_count_; }
private:
// If specified, will always return this layout.
std::optional<ProposedLayout> forced_layout_;
size_t layout_count_ = 0;
};
// This layout layout lays out included child views in the upper-left of the
// host view with kChildViewPadding around them. Views that will not fit are
// made invisible. Child views are expected to overlap as they all have the
// same top-left corner.
class MockLayoutManagerBase : public LayoutManagerBase {
public:
using LayoutManagerBase::AddOwnedLayout;
using LayoutManagerBase::InvalidateHost;
int num_invalidations() const { return num_invalidations_; }
int num_layouts_generated() const { return num_layouts_generated_; }
// LayoutManagerBase:
ProposedLayout CalculateProposedLayout(
const SizeBounds& size_bounds) const override {
ProposedLayout layout;
layout.host_size = {kChildViewPadding, kChildViewPadding};
for (views::View* it : host_view()->children()) {
if (!IsChildIncludedInLayout(it)) {
continue;
}
const gfx::Size preferred_size = it->GetPreferredSize({});
bool visible = false;
gfx::Rect bounds;
const int required_width = preferred_size.width() + 2 * kChildViewPadding;
const int required_height =
preferred_size.height() + 2 * kChildViewPadding;
if ((required_width <= size_bounds.width()) &&
(required_height <= size_bounds.height())) {
visible = true;
bounds = gfx::Rect(kChildViewPadding, kChildViewPadding,
preferred_size.width(), preferred_size.height());
layout.host_size.set_width(std::max(
layout.host_size.width(), bounds.right() + kChildViewPadding));
layout.host_size.set_height(std::max(
layout.host_size.height(), bounds.bottom() + kChildViewPadding));
}
layout.child_layouts.push_back({it, visible, bounds});
}
++num_layouts_generated_;
return layout;
}
void OnLayoutChanged() override {
LayoutManagerBase::OnLayoutChanged();
++num_invalidations_;
}
using LayoutManagerBase::ApplyLayout;
private:
mutable int num_layouts_generated_ = 0;
mutable int num_invalidations_ = 0;
};
void ExpectSameViews(const std::vector<const View*>& expected,
const std::vector<const View*>& actual) {
EXPECT_EQ(expected.size(), actual.size());
for (size_t i = 0; i < expected.size(); ++i) {
EXPECT_EQ(expected[i], actual[i]);
}
}
} // namespace
TEST(LayoutManagerBaseTest, GetMinimumSize) {
TestLayoutManagerBase layout;
EXPECT_EQ(kMinimumSize, layout.GetMinimumSize(nullptr));
}
TEST(LayoutManagerBaseTest, GetPreferredSize) {
TestLayoutManagerBase layout;
EXPECT_EQ(kPreferredSize, layout.GetPreferredSize(nullptr));
}
TEST(LayoutManagerBaseTest, GetPreferredHeightForWidth) {
constexpr int kWidth = 45;
TestLayoutManagerBase layout;
EXPECT_EQ(kPreferredSize.height(),
layout.GetPreferredHeightForWidth(nullptr, kWidth));
}
TEST(LayoutManagerBaseTest, Installed) {
auto layout_ptr = std::make_unique<TestLayoutManagerBase>();
LayoutManagerBase* layout = layout_ptr.get();
EXPECT_EQ(nullptr, layout->host_view());
View view;
view.SetLayoutManager(std::move(layout_ptr));
EXPECT_EQ(&view, layout->host_view());
}
TEST(LayoutManagerBaseTest, SetChildIncludedInLayout) {
View view;
View* const child1 = view.AddChildView(std::make_unique<View>());
View* const child2 = view.AddChildView(std::make_unique<View>());
View* const child3 = view.AddChildView(std::make_unique<View>());
auto layout_ptr = std::make_unique<TestLayoutManagerBase>();
TestLayoutManagerBase* layout = layout_ptr.get();
view.SetLayoutManager(std::move(layout_ptr));
// All views should be present.
ExpectSameViews({child1, child2, child3}, layout->GetIncludedChildViews());
// Remove one.
child2->SetProperty(kViewIgnoredByLayoutKey, true);
ExpectSameViews({child1, child3}, layout->GetIncludedChildViews());
// Remove another.
child1->SetProperty(kViewIgnoredByLayoutKey, true);
ExpectSameViews({child3}, layout->GetIncludedChildViews());
// Removing it again should have no effect.
child1->SetProperty(kViewIgnoredByLayoutKey, true);
ExpectSameViews({child3}, layout->GetIncludedChildViews());
// Add one back.
child1->SetProperty(kViewIgnoredByLayoutKey, false);
ExpectSameViews({child1, child3}, layout->GetIncludedChildViews());
// Adding it back again should have no effect.
child1->SetProperty(kViewIgnoredByLayoutKey, false);
ExpectSameViews({child1, child3}, layout->GetIncludedChildViews());
// Add the other view back.
child2->SetProperty(kViewIgnoredByLayoutKey, false);
ExpectSameViews({child1, child2, child3}, layout->GetIncludedChildViews());
}
TEST(LayoutManagerBaseTest, InvalidateHost_NotInstalled) {
MockLayoutManagerBase root_layout;
MockLayoutManagerBase* const child1 =
root_layout.AddOwnedLayout(std::make_unique<MockLayoutManagerBase>());
MockLayoutManagerBase* const child2 =
root_layout.AddOwnedLayout(std::make_unique<MockLayoutManagerBase>());
MockLayoutManagerBase* const grandchild =
child1->AddOwnedLayout(std::make_unique<MockLayoutManagerBase>());
root_layout.InvalidateHost(false);
EXPECT_EQ(0, root_layout.num_invalidations());
EXPECT_EQ(0, child1->num_invalidations());
EXPECT_EQ(0, child2->num_invalidations());
EXPECT_EQ(0, grandchild->num_invalidations());
child1->InvalidateHost(false);
EXPECT_EQ(0, root_layout.num_invalidations());
EXPECT_EQ(0, child1->num_invalidations());
EXPECT_EQ(0, child2->num_invalidations());
EXPECT_EQ(0, grandchild->num_invalidations());
child2->InvalidateHost(false);
EXPECT_EQ(0, root_layout.num_invalidations());
EXPECT_EQ(0, child1->num_invalidations());
EXPECT_EQ(0, child2->num_invalidations());
EXPECT_EQ(0, grandchild->num_invalidations());
grandchild->InvalidateHost(false);
EXPECT_EQ(0, root_layout.num_invalidations());
EXPECT_EQ(0, child1->num_invalidations());
EXPECT_EQ(0, child2->num_invalidations());
EXPECT_EQ(0, grandchild->num_invalidations());
root_layout.InvalidateHost(true);
EXPECT_EQ(1, root_layout.num_invalidations());
EXPECT_EQ(1, child1->num_invalidations());
EXPECT_EQ(1, child2->num_invalidations());
EXPECT_EQ(1, grandchild->num_invalidations());
child1->InvalidateHost(true);
EXPECT_EQ(2, root_layout.num_invalidations());
EXPECT_EQ(2, child1->num_invalidations());
EXPECT_EQ(2, child2->num_invalidations());
EXPECT_EQ(2, grandchild->num_invalidations());
child2->InvalidateHost(true);
EXPECT_EQ(3, root_layout.num_invalidations());
EXPECT_EQ(3, child1->num_invalidations());
EXPECT_EQ(3, child2->num_invalidations());
EXPECT_EQ(3, grandchild->num_invalidations());
grandchild->InvalidateHost(true);
EXPECT_EQ(4, root_layout.num_invalidations());
EXPECT_EQ(4, child1->num_invalidations());
EXPECT_EQ(4, child2->num_invalidations());
EXPECT_EQ(4, grandchild->num_invalidations());
}
TEST(LayoutManagerBaseTest, InvalidateHost_Installed) {
View view;
MockLayoutManagerBase* const root_layout =
view.SetLayoutManager(std::make_unique<MockLayoutManagerBase>());
MockLayoutManagerBase* const child1 =
root_layout->AddOwnedLayout(std::make_unique<MockLayoutManagerBase>());
MockLayoutManagerBase* const child2 =
root_layout->AddOwnedLayout(std::make_unique<MockLayoutManagerBase>());
MockLayoutManagerBase* const grandchild =
child1->AddOwnedLayout(std::make_unique<MockLayoutManagerBase>());
root_layout->InvalidateHost(false);
EXPECT_EQ(0, root_layout->num_invalidations());
EXPECT_EQ(0, child1->num_invalidations());
EXPECT_EQ(0, child2->num_invalidations());
EXPECT_EQ(0, grandchild->num_invalidations());
child1->InvalidateHost(false);
EXPECT_EQ(0, root_layout->num_invalidations());
EXPECT_EQ(0, child1->num_invalidations());
EXPECT_EQ(0, child2->num_invalidations());
EXPECT_EQ(0, grandchild->num_invalidations());
child2->InvalidateHost(false);
EXPECT_EQ(0, root_layout->num_invalidations());
EXPECT_EQ(0, child1->num_invalidations());
EXPECT_EQ(0, child2->num_invalidations());
EXPECT_EQ(0, grandchild->num_invalidations());
grandchild->InvalidateHost(false);
EXPECT_EQ(0, root_layout->num_invalidations());
EXPECT_EQ(0, child1->num_invalidations());
EXPECT_EQ(0, child2->num_invalidations());
EXPECT_EQ(0, grandchild->num_invalidations());
root_layout->InvalidateHost(true);
EXPECT_EQ(1, root_layout->num_invalidations());
EXPECT_EQ(1, child1->num_invalidations());
EXPECT_EQ(1, child2->num_invalidations());
EXPECT_EQ(1, grandchild->num_invalidations());
child1->InvalidateHost(true);
EXPECT_EQ(2, root_layout->num_invalidations());
EXPECT_EQ(2, child1->num_invalidations());
EXPECT_EQ(2, child2->num_invalidations());
EXPECT_EQ(2, grandchild->num_invalidations());
child2->InvalidateHost(true);
EXPECT_EQ(3, root_layout->num_invalidations());
EXPECT_EQ(3, child1->num_invalidations());
EXPECT_EQ(3, child2->num_invalidations());
EXPECT_EQ(3, grandchild->num_invalidations());
grandchild->InvalidateHost(true);
EXPECT_EQ(4, root_layout->num_invalidations());
EXPECT_EQ(4, child1->num_invalidations());
EXPECT_EQ(4, child2->num_invalidations());
EXPECT_EQ(4, grandchild->num_invalidations());
}
// Test LayoutManager functionality of LayoutManagerBase:
namespace {
// Base for tests that evaluate the LayoutManager functionality of
// LayoutManagerBase (rather than the LayoutManagerBase-specific behavior).
class LayoutManagerBaseManagerTest : public testing::Test {
public:
void SetUp() override {
host_view_ = std::make_unique<View>();
layout_manager_ =
host_view_->SetLayoutManager(std::make_unique<MockLayoutManagerBase>());
}
View* AddChildView(gfx::Size preferred_size) {
auto child = std::make_unique<StaticSizedView>(preferred_size);
return host_view_->AddChildView(std::move(child));
}
// Directly calls the layout manager's `Layout()` method, as if from within
// `View::Layout()` or an overridden version of the function.
void DoLayoutManagerLayout() { layout_manager_->Layout(host_view_.get()); }
View* host_view() { return host_view_.get(); }
MockLayoutManagerBase* layout_manager() { return layout_manager_; }
View* child(int index) { return host_view_->children().at(index); }
private:
std::unique_ptr<View> host_view_;
raw_ptr<MockLayoutManagerBase> layout_manager_;
};
} // namespace
TEST_F(LayoutManagerBaseManagerTest, ProposedLayout_GetLayoutFor) {
AddChildView(gfx::Size());
AddChildView(gfx::Size());
AddChildView(gfx::Size());
ProposedLayout layout;
constexpr gfx::Rect kChild1Bounds(3, 4, 10, 15);
layout.child_layouts.push_back({child(0), true, kChild1Bounds});
layout.child_layouts.push_back({child(1), false});
const ProposedLayout& const_layout = layout;
EXPECT_EQ(&layout.child_layouts[0], layout.GetLayoutFor(child(0)));
EXPECT_EQ(&const_layout.child_layouts[0],
const_layout.GetLayoutFor(child(0)));
EXPECT_EQ(&layout.child_layouts[1], layout.GetLayoutFor(child(1)));
EXPECT_EQ(&const_layout.child_layouts[1],
const_layout.GetLayoutFor(child(1)));
EXPECT_EQ(nullptr, layout.GetLayoutFor(child(2)));
EXPECT_EQ(nullptr, const_layout.GetLayoutFor(child(2)));
}
TEST_F(LayoutManagerBaseManagerTest, ApplyLayout) {
AddChildView(gfx::Size());
AddChildView(gfx::Size());
AddChildView(gfx::Size());
// We don't want to set the size of the host view because it will trigger a
// superfluous layout, so we'll just keep the old size and make sure it
// doesn't change.
const gfx::Size old_size = host_view()->size();
ProposedLayout layout;
// This should be ignored.
layout.host_size = {123, 456};
// Set the child visibility and bounds.
constexpr gfx::Rect kChild1Bounds(3, 4, 10, 15);
constexpr gfx::Rect kChild3Bounds(20, 21, 12, 14);
layout.child_layouts.push_back({child(0), true, kChild1Bounds});
layout.child_layouts.push_back({child(1), false});
layout.child_layouts.push_back({child(2), true, kChild3Bounds});
layout_manager()->ApplyLayout(layout);
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(kChild1Bounds, child(0)->bounds());
EXPECT_FALSE(child(1)->GetVisible());
EXPECT_TRUE(child(2)->GetVisible());
EXPECT_EQ(kChild3Bounds, child(2)->bounds());
EXPECT_EQ(old_size, host_view()->size());
}
TEST_F(LayoutManagerBaseManagerTest, ApplyLayout_SkipsOmittedViews) {
AddChildView(gfx::Size());
AddChildView(gfx::Size());
AddChildView(gfx::Size());
ProposedLayout layout;
// Set the child visibility and bounds.
constexpr gfx::Rect kChild1Bounds(3, 4, 10, 15);
constexpr gfx::Rect kChild2Bounds(1, 2, 3, 4);
layout.child_layouts.push_back({child(0), true, kChild1Bounds});
layout.child_layouts.push_back({child(2), false});
// We'll set the second child separately.
child(1)->SetVisible(true);
child(1)->SetBoundsRect(kChild2Bounds);
layout_manager()->ApplyLayout(layout);
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(kChild1Bounds, child(0)->bounds());
EXPECT_TRUE(child(1)->GetVisible());
EXPECT_EQ(kChild2Bounds, child(1)->bounds());
EXPECT_FALSE(child(2)->GetVisible());
}
TEST_F(LayoutManagerBaseManagerTest, Install) {
EXPECT_EQ(host_view(), layout_manager()->host_view());
}
TEST_F(LayoutManagerBaseManagerTest, GetMinimumSize) {
AddChildView(kSquarishSize);
AddChildView(kLongSize);
AddChildView(kTallSize);
EXPECT_EQ(gfx::Size(kChildViewPadding, kChildViewPadding),
host_view()->GetMinimumSize());
}
TEST_F(LayoutManagerBaseManagerTest, GetPreferredSize) {
AddChildView(kSquarishSize);
AddChildView(kLongSize);
AddChildView(kTallSize);
const gfx::Size expected(kLongSize.width() + 2 * kChildViewPadding,
kTallSize.height() + 2 * kChildViewPadding);
EXPECT_EQ(expected, host_view()->GetPreferredSize({}));
}
TEST_F(LayoutManagerBaseManagerTest, GetPreferredHeightForWidth) {
AddChildView(kSquarishSize);
AddChildView(kLargeSize);
const int expected = kSquarishSize.height() + 2 * kChildViewPadding;
EXPECT_EQ(expected,
layout_manager()->GetPreferredHeightForWidth(host_view(), 20));
EXPECT_EQ(1, layout_manager()->num_layouts_generated());
layout_manager()->GetPreferredHeightForWidth(host_view(), 20);
EXPECT_EQ(1, layout_manager()->num_layouts_generated());
layout_manager()->GetPreferredHeightForWidth(host_view(), 25);
EXPECT_EQ(2, layout_manager()->num_layouts_generated());
}
TEST_F(LayoutManagerBaseManagerTest, InvalidateLayout) {
// Some invalidation could have been triggered during setup.
const int old_num_invalidations = layout_manager()->num_invalidations();
host_view()->InvalidateLayout();
EXPECT_EQ(old_num_invalidations + 1, layout_manager()->num_invalidations());
}
TEST_F(LayoutManagerBaseManagerTest, Layout) {
constexpr gfx::Point kUpperLeft(kChildViewPadding, kChildViewPadding);
AddChildView(kSquarishSize);
AddChildView(kLongSize);
AddChildView(kTallSize);
// This should fit all of the child views and trigger layout.
host_view()->SetSize({40, 40});
EXPECT_EQ(1, layout_manager()->num_layouts_generated());
EXPECT_EQ(gfx::Rect(kUpperLeft, child(0)->GetPreferredSize({})),
child(0)->bounds());
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(gfx::Rect(kUpperLeft, child(1)->GetPreferredSize({})),
child(1)->bounds());
EXPECT_TRUE(child(1)->GetVisible());
EXPECT_EQ(gfx::Rect(kUpperLeft, child(2)->GetPreferredSize({})),
child(2)->bounds());
EXPECT_TRUE(child(2)->GetVisible());
// This should drop out some children.
host_view()->SetSize({25, 25});
EXPECT_EQ(2, layout_manager()->num_layouts_generated());
EXPECT_EQ(gfx::Rect(kUpperLeft, child(0)->GetPreferredSize({})),
child(0)->bounds());
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_FALSE(child(1)->GetVisible());
EXPECT_FALSE(child(2)->GetVisible());
}
TEST_F(LayoutManagerBaseManagerTest, IgnoresChildWithViewIgnoredByLayoutKey) {
AddChildView(kSquarishSize);
AddChildView(kLongSize);
AddChildView(kTallSize);
child(1)->SetProperty(kViewIgnoredByLayoutKey, true);
child(1)->SetSize(kLargeSize);
// Makes enough room for all views, and triggers layout.
host_view()->SetSize({50, 50});
EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
EXPECT_EQ(kLargeSize, child(1)->size());
EXPECT_EQ(child(2)->GetPreferredSize({}), child(2)->size());
}
TEST_F(LayoutManagerBaseManagerTest, ViewVisibilitySet) {
AddChildView(kSquarishSize);
AddChildView(kLongSize);
AddChildView(kTallSize);
child(1)->SetVisible(false);
// Makes enough room for all views, and triggers layout.
host_view()->SetSize({50, 50});
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
EXPECT_FALSE(child(1)->GetVisible());
EXPECT_TRUE(child(2)->GetVisible());
EXPECT_EQ(child(2)->GetPreferredSize({}), child(2)->size());
// Turn the second child view back on and verify it's present in the layout
// again.
child(1)->SetVisible(true);
test::RunScheduledLayout(host_view());
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
EXPECT_TRUE(child(1)->GetVisible());
EXPECT_EQ(child(1)->GetPreferredSize({}), child(1)->size());
EXPECT_TRUE(child(2)->GetVisible());
EXPECT_EQ(child(2)->GetPreferredSize({}), child(2)->size());
}
TEST_F(LayoutManagerBaseManagerTest, ViewAdded) {
AddChildView(kLongSize);
AddChildView(kTallSize);
// Makes enough room for all views, and triggers layout.
host_view()->SetSize({50, 50});
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
EXPECT_TRUE(child(1)->GetVisible());
EXPECT_EQ(child(1)->GetPreferredSize({}), child(1)->size());
// Add a new view and verify it is being laid out.
View* new_view = AddChildView(kSquarishSize);
test::RunScheduledLayout(host_view());
EXPECT_TRUE(new_view->GetVisible());
EXPECT_EQ(new_view->GetPreferredSize({}), new_view->size());
}
TEST_F(LayoutManagerBaseManagerTest, ViewAdded_NotVisible) {
AddChildView(kLongSize);
AddChildView(kTallSize);
// Makes enough room for all views, and triggers layout.
host_view()->SetSize({50, 50});
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
EXPECT_TRUE(child(1)->GetVisible());
EXPECT_EQ(child(1)->GetPreferredSize({}), child(1)->size());
// Add a new view that is not visible and ensure that the layout manager
// doesn't touch it during layout.
View* new_view = new StaticSizedView(kSquarishSize);
new_view->SetVisible(false);
host_view()->AddChildViewRaw(new_view);
test::RunScheduledLayout(host_view());
EXPECT_FALSE(new_view->GetVisible());
}
TEST_F(LayoutManagerBaseManagerTest, ViewRemoved) {
AddChildView(kSquarishSize);
View* const child_view = AddChildView(kLongSize);
AddChildView(kTallSize);
// Makes enough room for all views, and triggers layout.
host_view()->SetSize({50, 50});
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
EXPECT_TRUE(child(1)->GetVisible());
EXPECT_EQ(child(1)->GetPreferredSize({}), child(1)->size());
EXPECT_TRUE(child(2)->GetVisible());
EXPECT_EQ(child(2)->GetPreferredSize({}), child(2)->size());
std::unique_ptr<View> owned_child_view =
host_view()->RemoveChildViewT(child_view);
owned_child_view->SetSize(kLargeSize);
test::RunScheduledLayout(host_view());
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
EXPECT_TRUE(child(1)->GetVisible());
EXPECT_EQ(child(1)->GetPreferredSize({}), child(1)->size());
EXPECT_TRUE(owned_child_view->GetVisible());
EXPECT_EQ(kLargeSize, owned_child_view->size());
}
class TestIgnoredView : public View {
METADATA_HEADER(TestIgnoredView, View)
public:
TestIgnoredView() {
SetProperty(kViewIgnoredByLayoutKey, true);
SetBoundsRect(kExpectedBounds);
}
static constexpr gfx::Rect kExpectedBounds{1, 2, 10, 40};
};
BEGIN_METADATA(TestIgnoredView)
END_METADATA
// An ignored view's visibility and layout are managed outside of the layout
// manager, whether that's in the view itself or in a manual layout method in
// its parent view.
TEST_F(LayoutManagerBaseManagerTest, IgnoredView) {
View* child_view = AddChildView(kSquarishSize);
test::RunScheduledLayout(host_view());
gfx::Rect host_view_bounds = host_view()->bounds();
gfx::Rect child_view_bounds = child_view->bounds();
TestIgnoredView* ignored_view =
host_view()->AddChildView(std::make_unique<TestIgnoredView>());
ignored_view->SetVisible(true);
test::RunScheduledLayout(host_view());
// The ignored view does not affect the layout of other views.
EXPECT_EQ(host_view()->bounds(), host_view_bounds);
EXPECT_EQ(child_view->bounds(), child_view_bounds);
// The ignored view manages its own layout.
EXPECT_EQ(ignored_view->bounds(), ignored_view->kExpectedBounds);
// The ignored view's visibility shouldn't be changed by the layout manager.
ignored_view->SetVisible(false);
test::RunScheduledLayout(host_view());
EXPECT_EQ(ignored_view->GetVisible(), false);
EXPECT_EQ(ignored_view->bounds(), ignored_view->kExpectedBounds);
ignored_view->SetVisible(true);
test::RunScheduledLayout(host_view());
EXPECT_EQ(ignored_view->GetVisible(), true);
EXPECT_EQ(ignored_view->bounds(), ignored_view->kExpectedBounds);
}
TEST(LayoutManagerBase_ProposedLayoutTest, Equality) {
std::array<View, 3> views;
ProposedLayout a;
ProposedLayout b;
EXPECT_TRUE(a == b);
a.host_size = {1, 2};
EXPECT_FALSE(a == b);
b.host_size = {1, 2};
EXPECT_TRUE(a == b);
a.child_layouts.push_back({&views[0], true, {1, 1, 2, 2}});
EXPECT_FALSE(a == b);
b.child_layouts.push_back(a.child_layouts[0]);
EXPECT_TRUE(a == b);
a.child_layouts[0].visible = false;
EXPECT_FALSE(a == b);
b.child_layouts[0].visible = false;
EXPECT_TRUE(a == b);
b.child_layouts[0].bounds = {0, 0, 3, 3};
// Since |visible| == false, changing bounds doesn't change anything.
EXPECT_TRUE(a == b);
a.child_layouts[0].visible = true;
b.child_layouts[0].visible = true;
EXPECT_FALSE(a == b);
a.child_layouts[0].visible = false;
b.child_layouts[0].visible = false;
a.child_layouts.push_back({&views[1], true, {1, 2, 3, 4}});
b.child_layouts.push_back({&views[2], true, {1, 2, 3, 4}});
EXPECT_FALSE(a == b);
b.child_layouts[1].child_view = &views[1];
EXPECT_TRUE(a == b);
}
class LayoutManagerBaseAvailableSizeTest : public testing::Test {
public:
void SetUp() override {
view_ = std::make_unique<View>();
layout_ =
view_->SetLayoutManager(std::make_unique<TestLayoutManagerBase>());
}
View* view() { return view_.get(); }
TestLayoutManagerBase* layout() { return layout_; }
private:
std::unique_ptr<View> view_;
raw_ptr<TestLayoutManagerBase> layout_;
};
TEST_F(LayoutManagerBaseAvailableSizeTest, ReturnsCorrectValues) {
const SizeBounds kChild1Bounds(3, 7);
const SizeBounds kChild2Bounds(11, 13);
View* const child1 = view()->AddChildView(std::make_unique<View>());
View* const child2 = view()->AddChildView(std::make_unique<View>());
View not_a_child;
layout()->OverrideProposedLayout(
{{10, 10},
{{child1, true, {1, 1, 1, 1}, kChild1Bounds},
{child2, true, {2, 2, 2, 2}, kChild2Bounds}}});
view()->SizeToPreferredSize();
EXPECT_EQ(kChild1Bounds, view()->GetAvailableSize(child1));
EXPECT_EQ(kChild2Bounds, view()->GetAvailableSize(child2));
EXPECT_EQ(SizeBounds(), view()->GetAvailableSize(&not_a_child));
}
TEST_F(LayoutManagerBaseAvailableSizeTest, AvailableSizesInNestedValuesAdd) {
View* const child = view()->AddChildView(std::make_unique<View>());
View* const grandchild = child->AddChildView(std::make_unique<View>());
auto* const child_layout =
child->SetLayoutManager(std::make_unique<TestLayoutManagerBase>());
constexpr gfx::Size kViewSize(18, 17);
constexpr SizeBounds kChildAvailableSize(16, 15);
constexpr gfx::Size kChildSize(13, 12);
constexpr SizeBounds kGrandchildAvailableSize(10, 9);
constexpr gfx::Size kGrandchildSize(3, 2);
layout()->OverrideProposedLayout(
{kViewSize, {{child, true, {{3, 3}, kChildSize}, kChildAvailableSize}}});
child_layout->OverrideProposedLayout({kChildSize,
{{grandchild,
true,
{{2, 2}, kGrandchildSize},
kGrandchildAvailableSize}}});
view()->SizeToPreferredSize();
EXPECT_EQ(kChildAvailableSize, view()->GetAvailableSize(child));
SizeBounds expected;
expected.set_width(kGrandchildAvailableSize.width() +
kChildAvailableSize.width() - kChildSize.width());
expected.set_height(kGrandchildAvailableSize.height() +
kChildAvailableSize.height() - kChildSize.height());
EXPECT_EQ(expected, child->GetAvailableSize(grandchild));
}
TEST_F(LayoutManagerBaseAvailableSizeTest,
PartiallySpecifiedAvailableSizesInNestedLayoutsAddPartially) {
View* const child = view()->AddChildView(std::make_unique<View>());
View* const grandchild = child->AddChildView(std::make_unique<View>());
auto* const child_layout =
child->SetLayoutManager(std::make_unique<TestLayoutManagerBase>());
constexpr gfx::Size kViewSize(18, 17);
constexpr SizeBounds kChildAvailableSize(16, SizeBound());
constexpr gfx::Size kChildSize(13, 12);
constexpr SizeBounds kGrandchildAvailableSize(10, 9);
constexpr gfx::Size kGrandchildSize(3, 2);
layout()->OverrideProposedLayout(
{kViewSize, {{child, true, {{3, 3}, kChildSize}, kChildAvailableSize}}});
child_layout->OverrideProposedLayout({kChildSize,
{{grandchild,
true,
{{2, 2}, kGrandchildSize},
kGrandchildAvailableSize}}});
view()->SizeToPreferredSize();
EXPECT_EQ(kChildAvailableSize, view()->GetAvailableSize(child));
SizeBounds expected;
expected.set_width(kGrandchildAvailableSize.width() +
kChildAvailableSize.width() - kChildSize.width());
expected.set_height(kGrandchildAvailableSize.height());
EXPECT_EQ(expected, child->GetAvailableSize(grandchild));
}
TEST_F(LayoutManagerBaseAvailableSizeTest,
MismatchedAvailableSizesInNestedLayoutsDoNotAdd) {
View* const child = view()->AddChildView(std::make_unique<View>());
View* const grandchild = child->AddChildView(std::make_unique<View>());
auto* const child_layout =
child->SetLayoutManager(std::make_unique<TestLayoutManagerBase>());
constexpr gfx::Size kViewSize(18, 17);
constexpr SizeBounds kChildAvailableSize(16, SizeBound());
constexpr gfx::Size kChildSize(13, 12);
constexpr SizeBounds kGrandchildAvailableSize(SizeBound(), 9);
constexpr gfx::Size kGrandchildSize(3, 2);
layout()->OverrideProposedLayout(
{kViewSize, {{child, true, {{3, 3}, kChildSize}, kChildAvailableSize}}});
child_layout->OverrideProposedLayout({kChildSize,
{{grandchild,
true,
{{2, 2}, kGrandchildSize},
kGrandchildAvailableSize}}});
view()->SizeToPreferredSize();
EXPECT_EQ(kChildAvailableSize, view()->GetAvailableSize(child));
EXPECT_EQ(kGrandchildAvailableSize, child->GetAvailableSize(grandchild));
}
TEST_F(LayoutManagerBaseAvailableSizeTest,
AvaialbleSizeChangeTriggersDescendantLayout) {
View* const child = view()->AddChildView(std::make_unique<View>());
TestLayoutManagerBase* const child_layout =
child->SetLayoutManager(std::make_unique<TestLayoutManagerBase>());
View* const grandchild = child->AddChildView(std::make_unique<View>());
TestLayoutManagerBase* const grandchild_layout =
grandchild->SetLayoutManager(std::make_unique<TestLayoutManagerBase>());
View* const great_grandchild =
grandchild->AddChildView(std::make_unique<View>());
TestLayoutManagerBase* const great_grandchild_layout =
great_grandchild->SetLayoutManager(
std::make_unique<TestLayoutManagerBase>());
// Create a default root layout with non-visible, zero-size child with no
// available size.
ProposedLayout root_layout;
root_layout.child_layouts.emplace_back();
root_layout.child_layouts[0].child_view = child;
root_layout.child_layouts[0].available_size = SizeBounds(0, 0);
// Set some default layouts for the rest of the hierarchy.
layout()->OverrideProposedLayout(root_layout);
child_layout->OverrideProposedLayout({{}, {{grandchild, false, {}, {0, 0}}}});
grandchild_layout->OverrideProposedLayout(
{{}, {{great_grandchild, false, {}, {0, 0}}}});
test::RunScheduledLayout(view());
const size_t num_grandchild_layouts = grandchild_layout->layout_count();
const size_t num_great_grandchild_layouts =
great_grandchild_layout->layout_count();
// Set the same rootlayout again as a control. This should not have an effect
// on the layout of the grand- and great-grandchild views.
layout()->OverrideProposedLayout(root_layout);
test::RunScheduledLayout(view());
EXPECT_EQ(num_grandchild_layouts, grandchild_layout->layout_count());
EXPECT_EQ(num_great_grandchild_layouts,
great_grandchild_layout->layout_count());
// Now set the child view to be visible with nonzero size and even larger
// available size. Applying this layout should change the size available to
// all views down the hierarchy, forcing a re-layout.
root_layout.child_layouts[0].visible = true;
root_layout.child_layouts[0].bounds = gfx::Rect(0, 0, 5, 5);
root_layout.child_layouts[0].available_size = SizeBounds(10, 10);
layout()->OverrideProposedLayout(root_layout);
test::RunScheduledLayout(view());
EXPECT_EQ(num_grandchild_layouts + 1, grandchild_layout->layout_count());
EXPECT_EQ(num_great_grandchild_layouts + 1,
great_grandchild_layout->layout_count());
}
using ManualLayoutUtilTest = LayoutManagerBaseManagerTest;
TEST_F(ManualLayoutUtilTest, SetCanBeVisibleForManualLayout) {
ManualLayoutUtil manual_layout_util(layout_manager());
AddChildView(kPreferredSize);
AddChildView(kPreferredSize);
AddChildView(kPreferredSize);
host_view()->SizeToPreferredSize();
test::RunScheduledLayout(host_view());
const int old_invalidations = layout_manager()->num_invalidations();
const int old_layouts = layout_manager()->num_layouts_generated();
EXPECT_TRUE(child(0)->GetVisible());
const gfx::Rect old_child1_bounds = child(0)->bounds();
const gfx::Rect old_child2_bounds = child(1)->bounds();
// Hide the view and remove from the layout.
manual_layout_util.SetViewHidden(child(0), true);
EXPECT_FALSE(child(0)->GetVisible());
DoLayoutManagerLayout();
EXPECT_FALSE(child(0)->GetVisible());
// The second child should now have moved over into the space of the first.
EXPECT_EQ(old_child1_bounds, child(1)->bounds());
// These lines should have no effect as it is already set to hidden.
manual_layout_util.SetViewHidden(child(0), true);
DoLayoutManagerLayout();
EXPECT_FALSE(child(0)->GetVisible());
EXPECT_EQ(old_child1_bounds, child(1)->bounds());
// Enable the view in the layout again. This won't immediately cause the view
// to reappear; it may be made visible during the next layout pass.
manual_layout_util.SetViewHidden(child(0), false);
EXPECT_FALSE(child(0)->GetVisible());
DoLayoutManagerLayout();
EXPECT_TRUE(child(0)->GetVisible());
// The first child should be in its original bounds.
EXPECT_EQ(old_child1_bounds, child(0)->bounds());
// The second child should move back to its original bounds.
EXPECT_EQ(old_child2_bounds, child(1)->bounds());
// Again, this should have no effect as the view is already enabled.
manual_layout_util.SetViewHidden(child(0), false);
DoLayoutManagerLayout();
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(old_child1_bounds, child(0)->bounds());
EXPECT_EQ(old_child2_bounds, child(1)->bounds());
EXPECT_EQ(old_layouts + 2, layout_manager()->num_layouts_generated());
EXPECT_EQ(old_invalidations + 2, layout_manager()->num_invalidations());
}
TEST_F(ManualLayoutUtilTest, TemporarilyExcludeOneViewFromLayout) {
ManualLayoutUtil manual_layout_util(layout_manager());
AddChildView(kPreferredSize);
AddChildView(kPreferredSize);
AddChildView(kPreferredSize);
host_view()->SizeToPreferredSize();
test::RunScheduledLayout(host_view());
const int old_invalidations = layout_manager()->num_invalidations();
const int old_layouts = layout_manager()->num_layouts_generated();
EXPECT_TRUE(child(0)->GetVisible());
const gfx::Rect old_child1_bounds = child(0)->bounds();
const gfx::Rect old_child2_bounds = child(1)->bounds();
{
auto exclusion = manual_layout_util.TemporarilyExcludeFromLayout(child(0));
DoLayoutManagerLayout();
// Views should have been slid over in the place of the excluded view.
EXPECT_EQ(old_child1_bounds, child(1)->bounds());
// Excluded view should still be visible and in its previous location.
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(old_child1_bounds, child(0)->bounds());
EXPECT_EQ(old_invalidations + 1, layout_manager()->num_invalidations());
EXPECT_EQ(old_layouts + 1, layout_manager()->num_layouts_generated());
}
// Exiting the block should cause the layout to be restored.
DoLayoutManagerLayout();
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(old_child1_bounds, child(0)->bounds());
EXPECT_EQ(old_child2_bounds, child(1)->bounds());
EXPECT_EQ(old_invalidations + 2, layout_manager()->num_invalidations());
EXPECT_EQ(old_layouts + 2, layout_manager()->num_layouts_generated());
}
TEST_F(ManualLayoutUtilTest, TemporarilyExcludeTwoViewsFromLayout) {
ManualLayoutUtil manual_layout_util(layout_manager());
AddChildView(kPreferredSize);
AddChildView(kPreferredSize);
AddChildView(kPreferredSize);
host_view()->SizeToPreferredSize();
test::RunScheduledLayout(host_view());
const int old_invalidations = layout_manager()->num_invalidations();
const int old_layouts = layout_manager()->num_layouts_generated();
EXPECT_TRUE(child(0)->GetVisible());
const gfx::Rect old_child1_bounds = child(0)->bounds();
const gfx::Rect old_child2_bounds = child(1)->bounds();
const gfx::Rect old_child3_bounds = child(2)->bounds();
{
auto exclusion1 = manual_layout_util.TemporarilyExcludeFromLayout(child(0));
auto exclusion2 = manual_layout_util.TemporarilyExcludeFromLayout(child(1));
DoLayoutManagerLayout();
// Views should have been slid over in the place of the excluded view.
EXPECT_EQ(old_child1_bounds, child(2)->bounds());
// Excluded view should still be visible and in its previous location.
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_TRUE(child(1)->GetVisible());
EXPECT_EQ(old_child1_bounds, child(0)->bounds());
EXPECT_EQ(old_child2_bounds, child(1)->bounds());
EXPECT_EQ(old_invalidations + 2, layout_manager()->num_invalidations());
EXPECT_EQ(old_layouts + 1, layout_manager()->num_layouts_generated());
}
// Exiting the block should cause the layout to be restored.
DoLayoutManagerLayout();
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_TRUE(child(1)->GetVisible());
EXPECT_EQ(old_child1_bounds, child(0)->bounds());
EXPECT_EQ(old_child2_bounds, child(1)->bounds());
EXPECT_EQ(old_child3_bounds, child(2)->bounds());
EXPECT_EQ(old_invalidations + 4, layout_manager()->num_invalidations());
EXPECT_EQ(old_layouts + 2, layout_manager()->num_layouts_generated());
}
TEST_F(ManualLayoutUtilTest, ViewStaysExcludedIfAlreadyExcludedByProperty) {
ManualLayoutUtil manual_layout_util(layout_manager());
AddChildView(kPreferredSize);
AddChildView(kPreferredSize);
AddChildView(kPreferredSize);
host_view()->SizeToPreferredSize();
test::RunScheduledLayout(host_view());
EXPECT_TRUE(child(0)->GetVisible());
const gfx::Rect old_child1_bounds = child(0)->bounds();
child(0)->SetProperty(kViewIgnoredByLayoutKey, true);
test::RunScheduledLayout(host_view());
const int old_invalidations = layout_manager()->num_invalidations();
const int old_layouts = layout_manager()->num_layouts_generated();
{
// This will have no effect as the view is already excluded.
auto exclusion = manual_layout_util.TemporarilyExcludeFromLayout(child(0));
DoLayoutManagerLayout();
// Views should have been slid over in the place of the excluded view.
EXPECT_EQ(old_child1_bounds, child(1)->bounds());
// Excluded view should still be visible and in its previous location.
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(old_child1_bounds, child(0)->bounds());
EXPECT_EQ(old_invalidations, layout_manager()->num_invalidations());
EXPECT_EQ(old_layouts, layout_manager()->num_layouts_generated());
}
// Exiting the block should have no effect.
DoLayoutManagerLayout();
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(old_child1_bounds, child(0)->bounds());
EXPECT_EQ(old_child1_bounds, child(1)->bounds());
EXPECT_EQ(old_invalidations, layout_manager()->num_invalidations());
EXPECT_EQ(old_layouts, layout_manager()->num_layouts_generated());
}
TEST_F(ManualLayoutUtilTest, ViewHiddenDuringTemporaryExclusion) {
ManualLayoutUtil manual_layout_util(layout_manager());
AddChildView(kPreferredSize);
AddChildView(kPreferredSize);
AddChildView(kPreferredSize);
host_view()->SizeToPreferredSize();
test::RunScheduledLayout(host_view());
EXPECT_TRUE(child(0)->GetVisible());
const gfx::Rect old_child1_bounds = child(0)->bounds();
const int old_invalidations = layout_manager()->num_invalidations();
const int old_layouts = layout_manager()->num_layouts_generated();
{
// This will have no effect as the view is already excluded.
auto exclusion = manual_layout_util.TemporarilyExcludeFromLayout(child(0));
DoLayoutManagerLayout();
// Views should have been slid over in the place of the excluded view.
EXPECT_EQ(old_child1_bounds, child(1)->bounds());
// Excluded view should still be visible and in its previous location.
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(old_child1_bounds, child(0)->bounds());
EXPECT_EQ(old_invalidations + 1, layout_manager()->num_invalidations());
EXPECT_EQ(old_layouts + 1, layout_manager()->num_layouts_generated());
// This changes the view to hidden, which "stacks" with exclusion. Since it
// is a different property, it will invalidate the layout.
manual_layout_util.SetViewHidden(child(0), true);
DoLayoutManagerLayout();
EXPECT_FALSE(child(0)->GetVisible());
EXPECT_EQ(old_child1_bounds, child(1)->bounds());
EXPECT_EQ(old_invalidations + 2, layout_manager()->num_invalidations());
EXPECT_EQ(old_layouts + 2, layout_manager()->num_layouts_generated());
}
// Exiting the block will cause an invalidation, but since visibility
// overrides inclusion, it will not change the layout.
DoLayoutManagerLayout();
EXPECT_FALSE(child(0)->GetVisible());
EXPECT_EQ(old_child1_bounds, child(1)->bounds());
EXPECT_EQ(old_invalidations + 3, layout_manager()->num_invalidations());
EXPECT_EQ(old_layouts + 3, layout_manager()->num_layouts_generated());
}
TEST_F(ManualLayoutUtilTest, ViewUnhiddenDuringTemporaryExclusion) {
ManualLayoutUtil manual_layout_util(layout_manager());
AddChildView(kPreferredSize);
AddChildView(kPreferredSize);
AddChildView(kPreferredSize);
child(0)->SetVisible(false);
host_view()->SizeToPreferredSize();
test::RunScheduledLayout(host_view());
EXPECT_FALSE(child(0)->GetVisible());
const gfx::Rect old_child2_bounds = child(1)->bounds();
const gfx::Rect old_child3_bounds = child(2)->bounds();
const int old_invalidations = layout_manager()->num_invalidations();
const int old_layouts = layout_manager()->num_layouts_generated();
{
// This will have no effect as the view is already excluded, but will cause
// another invalidation since exclusion and visibility are different
// properties.
auto exclusion = manual_layout_util.TemporarilyExcludeFromLayout(child(0));
DoLayoutManagerLayout();
// Views should have been slid over in the place of the hidden view.
EXPECT_EQ(old_child2_bounds, child(1)->bounds());
// Excluded and hidden view should not be visible.
EXPECT_FALSE(child(0)->GetVisible());
EXPECT_EQ(old_invalidations + 1, layout_manager()->num_invalidations());
EXPECT_EQ(old_layouts + 1, layout_manager()->num_layouts_generated());
// This changes the view to not hidden. While this would not affect the
// layout due to the exclusion, but since it's a different property, it
// still triggers an invalidation.
manual_layout_util.SetViewHidden(child(0), false);
// Note that since the child view is still excluded from the layout, it
// retains its existing state, including visibility.
DoLayoutManagerLayout();
EXPECT_FALSE(child(0)->GetVisible());
EXPECT_EQ(old_child2_bounds, child(1)->bounds());
EXPECT_EQ(old_invalidations + 2, layout_manager()->num_invalidations());
EXPECT_EQ(old_layouts + 2, layout_manager()->num_layouts_generated());
}
// Exiting the block will re-add the view, which is now made visible by the
// layout.
DoLayoutManagerLayout();
EXPECT_TRUE(child(0)->GetVisible());
EXPECT_EQ(old_child2_bounds, child(0)->bounds());
EXPECT_EQ(old_child3_bounds, child(1)->bounds());
EXPECT_EQ(old_invalidations + 3, layout_manager()->num_invalidations());
EXPECT_EQ(old_layouts + 3, layout_manager()->num_layouts_generated());
}
} // namespace views