blob: f616d87a642e3c0c06721bf3b81dccc1699a7b95 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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/numerics/ranges.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/views/test/test_layout_manager.h"
#include "ui/views/test/test_views.h"
#include "ui/views/view.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::copy_if(host_view()->children().begin(), host_view()->children().end(),
std::back_inserter(included), [=](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(
base::ClampToRange<SizeBound>(size_bounds.width(), kMinimumSize.width(),
kPreferredSize.width())
.value());
layout.host_size.set_height(base::ClampToRange<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.
base::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 (auto* 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.
layout->SetChildViewIgnoredByLayout(child2, true);
ExpectSameViews({child1, child3}, layout->GetIncludedChildViews());
// Remove another.
layout->SetChildViewIgnoredByLayout(child1, true);
ExpectSameViews({child3}, layout->GetIncludedChildViews());
// Removing it again should have no effect.
layout->SetChildViewIgnoredByLayout(child1, true);
ExpectSameViews({child3}, layout->GetIncludedChildViews());
// Add one back.
layout->SetChildViewIgnoredByLayout(child1, false);
ExpectSameViews({child1, child3}, layout->GetIncludedChildViews());
// Adding it back again should have no effect.
layout->SetChildViewIgnoredByLayout(child1, false);
ExpectSameViews({child1, child3}, layout->GetIncludedChildViews());
// Add the other view back.
layout->SetChildViewIgnoredByLayout(child2, 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));
}
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_;
MockLayoutManagerBase* layout_manager_;
};
} // namespace
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, ChildViewIgnoredByLayout) {
AddChildView(kSquarishSize);
AddChildView(kLongSize);
AddChildView(kTallSize);
EXPECT_FALSE(layout_manager()->IsChildViewIgnoredByLayout(child(0)));
EXPECT_FALSE(layout_manager()->IsChildViewIgnoredByLayout(child(1)));
EXPECT_FALSE(layout_manager()->IsChildViewIgnoredByLayout(child(2)));
layout_manager()->SetChildViewIgnoredByLayout(child(1), true);
EXPECT_FALSE(layout_manager()->IsChildViewIgnoredByLayout(child(0)));
EXPECT_TRUE(layout_manager()->IsChildViewIgnoredByLayout(child(1)));
EXPECT_FALSE(layout_manager()->IsChildViewIgnoredByLayout(child(2)));
}
TEST_F(LayoutManagerBaseManagerTest,
ChildViewIgnoredByLayout_IgnoresChildView) {
AddChildView(kSquarishSize);
AddChildView(kLongSize);
AddChildView(kTallSize);
layout_manager()->SetChildViewIgnoredByLayout(child(1), 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);
host_view()->Layout();
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);
host_view()->Layout();
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()->AddChildView(new_view);
host_view()->Layout();
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());
host_view()->RemoveChildView(child_view);
child_view->SetSize(kLargeSize);
host_view()->Layout();
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_view->GetVisible());
EXPECT_EQ(kLargeSize, child_view->size());
// Required since we removed it from the parent view.
delete child_view;
}
TEST(LayoutManagerBase_ProposedLayoutTest, Equality) {
View* ptr0 = nullptr;
View* ptr1 = ptr0 + 1;
View* ptr2 = ptr0 + 2;
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({ptr0, 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({ptr1, true, {1, 2, 3, 4}});
b.child_layouts.push_back({ptr2, true, {1, 2, 3, 4}});
EXPECT_FALSE(a == b);
b.child_layouts[1].child_view = ptr1;
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_;
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}}}});
view()->Layout();
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);
view()->Layout();
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);
view()->Layout();
EXPECT_EQ(num_grandchild_layouts + 1, grandchild_layout->layout_count());
EXPECT_EQ(num_great_grandchild_layouts + 1,
great_grandchild_layout->layout_count());
}
} // namespace views