blob: 735d65eedc76a7cfa9d8bd4acb6ba161191eb01a [file]
// Copyright 2012 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/box_layout.h"
#include <algorithm>
#include <numeric>
#include <string>
#include <vector>
#include "base/containers/adapters.h"
#include "base/notimplemented.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/layout/normalized_geometry.h"
#include "ui/views/view_class_properties.h"
namespace views {
namespace {
struct BoxChildData {
BoxChildData() = default;
// Copying this struct would be expensive and they only ever live in a vector
// in Layout (see below) so we'll only allow move semantics.
BoxChildData(const BoxChildData&) = delete;
BoxChildData& operator=(const BoxChildData&) = delete;
BoxChildData(BoxChildData&& other) = default;
std::string ToString() const {
return base::StrCat({"{ preferred ", preferred_size.ToString(), " margins ",
margins.ToString(), " bounds ",
actual_bounds.ToString(), " }"});
}
NormalizedSize preferred_size;
NormalizedInsets margins;
NormalizedRect actual_bounds;
int flex;
};
} // namespace
struct BoxLayout::BoxLayoutData {
BoxLayoutData() = default;
BoxLayoutData(const BoxLayoutData&) = delete;
BoxLayoutData& operator=(const BoxLayoutData&) = delete;
~BoxLayoutData() = default;
size_t num_children() const { return child_data.size(); }
std::string ToString() const {
std::string result = base::StrCat({"{ host_size ", total_size.ToString(),
", cross_center_pos ",
base::NumberToString(cross_center_pos),
"\n", layout.ToString(), " {\n"});
bool first = true;
for (const BoxChildData& flex_child : child_data) {
if (first) {
first = false;
} else {
base::StrAppend(&result, {",\n"});
}
base::StrAppend(&result, {flex_child.ToString()});
}
base::StrAppend(&result,
{"}\nmargin ", interior_margin.ToString(), " insets ",
host_insets.ToString(), "\nmax_cross_margin ",
max_cross_margin.ToString()});
return result;
}
ProposedLayout layout;
// Holds additional information about the child views of this layout.
std::vector<BoxChildData> child_data;
NormalizedSize total_size;
NormalizedInsets host_insets;
NormalizedInsets interior_margin;
Inset1D max_cross_margin;
int cross_center_pos;
};
// BoxLayoutFlexSpecification --------------------------------------------------
BoxLayoutFlexSpecification::BoxLayoutFlexSpecification() = default;
BoxLayoutFlexSpecification BoxLayoutFlexSpecification::WithWeight(
int weight) const {
DCHECK_GE(weight, 0);
BoxLayoutFlexSpecification spec = *this;
spec.weight_ = weight;
return spec;
}
BoxLayoutFlexSpecification BoxLayoutFlexSpecification::UseMinSize(
bool use_min_size) const {
BoxLayoutFlexSpecification spec = *this;
spec.use_min_size_ = use_min_size;
return spec;
}
BoxLayoutFlexSpecification::~BoxLayoutFlexSpecification() = default;
// BoxLayout --------------------------------------------------
BoxLayout::BoxLayout(BoxLayout::Orientation orientation,
const gfx::Insets& inside_border_insets,
int between_child_spacing,
bool collapse_margins_spacing)
: orientation_(orientation),
inside_border_insets_(inside_border_insets),
between_child_spacing_(between_child_spacing),
collapse_margins_spacing_(collapse_margins_spacing) {}
BoxLayout::~BoxLayout() = default;
void BoxLayout::SetOrientation(Orientation orientation) {
if (orientation_ != orientation) {
orientation_ = orientation;
InvalidateHost(true);
}
}
BoxLayout::Orientation BoxLayout::GetOrientation() const {
return orientation_;
}
void BoxLayout::set_main_axis_alignment(MainAxisAlignment main_axis_alignment) {
if (main_axis_alignment_ != main_axis_alignment) {
main_axis_alignment_ = main_axis_alignment;
InvalidateHost(true);
}
}
void BoxLayout::set_cross_axis_alignment(
CrossAxisAlignment cross_axis_alignment) {
if (cross_axis_alignment_ != cross_axis_alignment) {
cross_axis_alignment_ = cross_axis_alignment;
InvalidateHost(true);
}
}
void BoxLayout::set_inside_border_insets(const gfx::Insets& insets) {
if (inside_border_insets_ != insets) {
inside_border_insets_ = insets;
InvalidateHost(true);
}
}
void BoxLayout::SetCollapseMarginsSpacing(bool collapse_margins_spacing) {
if (collapse_margins_spacing != collapse_margins_spacing_) {
collapse_margins_spacing_ = collapse_margins_spacing;
InvalidateHost(true);
}
}
bool BoxLayout::GetCollapseMarginsSpacing() const {
return collapse_margins_spacing_;
}
void BoxLayout::SetFlexForView(const View* view,
int flex_weight,
bool use_min_size) {
DCHECK(view);
DCHECK_EQ(host_view(), view->parent());
DCHECK_GE(flex_weight, 0);
const_cast<View*>(view)->SetProperty(kBoxLayoutFlexKey,
BoxLayoutFlexSpecification()
.WithWeight(flex_weight)
.UseMinSize(use_min_size));
}
void BoxLayout::ClearFlexForView(const View* view) {
DCHECK(view);
const_cast<View*>(view)->ClearProperty(kBoxLayoutFlexKey);
}
void BoxLayout::SetDefaultFlex(int default_flex) {
DCHECK_GE(default_flex, 0);
default_flex_ = default_flex;
}
int BoxLayout::GetDefaultFlex() const {
return default_flex_;
}
ProposedLayout BoxLayout::CalculateProposedLayout(
const SizeBounds& size_bounds) const {
BoxLayoutData data;
gfx::Insets insets = host_view()->GetInsets();
data.interior_margin = Normalize(orientation_, inside_border_insets_);
InitializeChildData(data);
// If the layout is stretched, and no constraints are specified, we need to
// calculate the maximum width of the child elements.
SizeBounds new_bounds(size_bounds);
if (orientation_ == Orientation::kVertical &&
cross_axis_alignment_ == CrossAxisAlignment::kStretch &&
!size_bounds.width().is_bounded()) {
new_bounds.set_width(CalculateMaxChildWidth(data));
}
NormalizedSizeBounds bounds = Normalize(orientation_, new_bounds);
// When |collapse_margins_spacing_ = true|, the host_insets include the
// leading of the first element and the trailing of the last one. It's crucial
// to keep this in mind while reading here. Conversely, they are not included.
if (collapse_margins_spacing_) {
Inset1D main_axis_inset(
data.child_data.empty()
? 0
: data.child_data.front().margins.main_leading(),
data.child_data.empty()
? 0
: data.child_data.back().margins.main_trailing());
data.host_insets = Normalize(
orientation_,
insets + Denormalize(orientation_,
NormalizedInsets(main_axis_inset, Inset1D())));
} else {
data.host_insets = Normalize(orientation_, insets + inside_border_insets_);
}
bounds.Inset(data.host_insets);
CalculatePreferredSize(Denormalize(orientation_, bounds), data);
// Calculate the size of the view without boundary constraints. This is the
// default size of our view.
CalculatePreferredTotalSize(data);
// Update the position information of the child views.
UpdateFlexLayout(bounds, data);
NormalizedSize host_size = data.total_size;
host_size.Enlarge(data.host_insets.main_size(),
data.host_insets.cross_size());
data.layout.host_size = Denormalize(orientation_, host_size);
CalculateChildBounds(size_bounds, data);
return data.layout;
}
int BoxLayout::GetFlexForView(const View* view) const {
// Respect flex provided via the `kBoxLayoutFlexKey`.
if (auto* flex_behavior_key = view->GetProperty(kBoxLayoutFlexKey)) {
return flex_behavior_key->weight();
}
// Fall back to default.
return default_flex_;
}
int BoxLayout::GetMinimumSizeForView(const View* view) const {
auto* flex_behavior_key = view->GetProperty(kBoxLayoutFlexKey);
if (!flex_behavior_key || !flex_behavior_key->use_min_size()) {
return 0;
}
return (orientation_ == Orientation::kHorizontal)
? view->GetMinimumSize().width()
: view->GetMinimumSize().height();
}
gfx::Size BoxLayout::GetPreferredSizeForView(
const View* view,
const NormalizedSizeBounds& size_bounds) const {
return view->GetPreferredSize(Denormalize(orientation_, size_bounds));
}
void BoxLayout::EnsureCrossSize(BoxLayoutData& data) const {
if (minimum_cross_axis_size_ > data.total_size.cross()) {
if (cross_axis_alignment_ == CrossAxisAlignment::kCenter) {
data.cross_center_pos +=
(minimum_cross_axis_size_ - data.total_size.cross()) / 2;
}
data.total_size.set_cross(minimum_cross_axis_size_);
}
}
void BoxLayout::InitializeChildData(BoxLayoutData& data) const {
int leading = 0;
int trailing = 0;
for (View* child : host_view()->children()) {
// If we calculate here in View::ChildVisibilityChanged, LayoutManagerBase
// will not modify the visibility property of the view in time, so we need
// to explicitly judge here.
if (!IsChildIncludedInLayout(child, true) || !child->GetVisible()) {
continue;
}
data.child_data.emplace_back();
BoxChildData& child_data = data.child_data.back();
data.layout.child_layouts.emplace_back(child, true);
gfx::Insets* margins = child ? child->GetProperty(kMarginsKey) : nullptr;
if (margins) {
child_data.margins = Normalize(orientation_, *margins);
}
child_data.flex = GetFlexForView(child);
leading = std::max(leading, child_data.margins.cross_leading());
trailing = std::max(trailing, child_data.margins.cross_trailing());
}
UpdateChildMarginsIfCollapseMarginsSpacing(data);
data.max_cross_margin.set_leading(leading);
data.max_cross_margin.set_trailing(trailing);
data.cross_center_pos = 0;
}
SizeBound BoxLayout::CalculateMaxChildWidth(BoxLayoutData& data) const {
gfx::Rect child_view_area;
SizeBound width = 0;
for (size_t i = 0; i < data.num_children(); ++i) {
BoxChildData& box_child = data.child_data[i];
ChildLayout& child_layout = data.layout.child_layouts[i];
gfx::Size child_size =
child_layout.child_view->GetPreferredSize({/* Unbounded */});
const NormalizedInsets& child_margins = box_child.margins;
// The value of |cross_axis_alignment_| will determine how the view's
// margins interact with each other or the |inside_border_insets_|.
if (cross_axis_alignment_ == CrossAxisAlignment::kStart) {
width = std::max<SizeBound>(
width, child_size.width() + child_margins.cross_trailing());
} else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
width = std::max<SizeBound>(
width, child_size.width() + child_margins.cross_leading());
} else {
// We don't have a rectangle which can be used to calculate a common
// center-point, so a single known point (0) along the horizontal axis
// is used. This is OK because we're only interested in the overall
// width and not the position.
gfx::Rect child_bounds =
gfx::Rect(-(child_size.width() / 2), 0, child_size.width(),
child_size.height());
child_bounds.Inset(gfx::Insets::TLBR(0, -child_margins.cross_leading(), 0,
-child_margins.cross_trailing()));
child_view_area.Union(child_bounds);
width = std::max<SizeBound>(width, child_view_area.width());
}
}
int extra_cross_margin = host_view()->GetInsets().width();
if (cross_axis_alignment_ == CrossAxisAlignment::kStart) {
extra_cross_margin = data.max_cross_margin.leading();
} else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
extra_cross_margin = data.max_cross_margin.trailing();
}
if (!collapse_margins_spacing_) {
extra_cross_margin += data.interior_margin.cross_size();
}
return std::max<SizeBound>(width + extra_cross_margin,
minimum_cross_axis_size_);
}
void BoxLayout::CalculatePreferredSize(const SizeBounds& bounds,
BoxLayoutData& data) const {
if (orientation_ == Orientation::kVertical) {
for (size_t i = 0; i < data.num_children(); ++i) {
BoxChildData& box_child = data.child_data[i];
ChildLayout& child_layout = data.layout.child_layouts[i];
SizeBound available_width = std::max<SizeBound>(
0, bounds.width() - box_child.margins.cross_size());
box_child.preferred_size = Normalize(
orientation_, child_layout.child_view->GetPreferredSize(
SizeBounds(available_width, bounds.height())));
}
} else {
for (size_t i = 0; i < data.num_children(); ++i) {
BoxChildData& box_child = data.child_data[i];
ChildLayout& child_layout = data.layout.child_layouts[i];
box_child.preferred_size = Normalize(
orientation_, child_layout.child_view->GetPreferredSize(bounds));
}
}
}
void BoxLayout::UpdateChildMarginsIfCollapseMarginsSpacing(
BoxLayoutData& data) const {
if (!collapse_margins_spacing_) {
return;
}
const size_t num_child = data.num_children();
for (size_t i = 0; i < num_child; ++i) {
BoxChildData& box_child = data.child_data[i];
int prev_trailing = i == 0 ? data.interior_margin.main_leading()
: data.child_data[i - 1].margins.main_trailing();
int next_leading = i == num_child - 1
? data.interior_margin.main_trailing()
: data.child_data[i + 1].margins.main_leading();
box_child.margins = NormalizedInsets(
std::max(box_child.margins.main_leading(), prev_trailing),
std::max(box_child.margins.cross_leading(),
data.interior_margin.cross_leading()),
std::max(box_child.margins.main_trailing(), next_leading),
std::max(box_child.margins.cross_trailing(),
data.interior_margin.cross_trailing()));
}
}
void BoxLayout::CalculatePreferredTotalSize(BoxLayoutData& data) const {
for (size_t i = 0; i < data.num_children(); ++i) {
BoxChildData& box_child = data.child_data[i];
int main_size = box_child.preferred_size.main();
if (!collapse_margins_spacing_) {
main_size += box_child.margins.main_size();
}
if (main_size == 0 && box_child.flex == 0) {
continue;
}
const NormalizedInsets& child_margins = box_child.margins;
if (i < data.num_children() - 1) {
if (collapse_margins_spacing_) {
main_size +=
std::max(between_child_spacing_, child_margins.main_trailing());
} else {
main_size += between_child_spacing_;
}
}
int cross_size = box_child.preferred_size.cross();
if (cross_axis_alignment_ == CrossAxisAlignment::kStart) {
cross_size +=
data.max_cross_margin.leading() + child_margins.cross_trailing();
} else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
cross_size +=
data.max_cross_margin.trailing() + child_margins.cross_leading();
} else {
// We implement center alignment by moving the central axis.
int view_center = box_child.preferred_size.cross() / 2;
int old_cross_center_pos = data.cross_center_pos;
data.cross_center_pos = std::max(
data.cross_center_pos, child_margins.cross_leading() + view_center);
cross_size = data.cross_center_pos + box_child.preferred_size.cross() -
view_center + child_margins.cross_trailing();
// If the new center point has moved to the right relative to the original
// center point, then we need to move all the views to the right, so the
// original total size increases by |data.cross_center_pos -
// old_cross_center_pos|.
data.total_size.Enlarge(
0, std::max(0, data.cross_center_pos - old_cross_center_pos));
}
data.total_size.SetSize(data.total_size.main() + main_size,
std::max(data.total_size.cross(), cross_size));
}
EnsureCrossSize(data);
}
void BoxLayout::UpdateFlexLayout(const NormalizedSizeBounds& bounds,
BoxLayoutData& data) const {
if (bounds.main() == 0 && bounds.cross() == 0) {
return;
}
int total_main_axis_size = data.total_size.main();
int flex_sum = std::accumulate(
data.child_data.cbegin(), data.child_data.cend(), 0,
[](int total, const BoxChildData& data) { return total + data.flex; });
// `main_free_space` can be negative. The free space is distributed to each
// view proportionally to their flex value.
SizeBound main_free_space = bounds.main() - total_main_axis_size;
int total_padding = 0;
int current_flex = 0;
const size_t num_child = data.num_children();
const int preferred_cross = data.total_size.cross();
data.total_size = NormalizedSize();
data.cross_center_pos = 0;
for (size_t i = 0; i < num_child; ++i) {
BoxChildData& box_child = data.child_data[i];
ChildLayout& child_layout = data.layout.child_layouts[i];
const NormalizedInsets& child_margins = box_child.margins;
if (!collapse_margins_spacing_) {
data.total_size.Enlarge(box_child.margins.main_leading(), 0);
}
box_child.actual_bounds.set_origin_main(data.total_size.main());
SizeBound cross_axis_size =
bounds.cross().is_bounded() && bounds.cross().value() > 0
? bounds.cross()
: preferred_cross;
if (cross_axis_alignment_ == CrossAxisAlignment::kStretch ||
cross_axis_alignment_ == CrossAxisAlignment::kCenter) {
cross_axis_size -= child_margins.cross_size();
}
// Calculate flex padding.
int current_padding = 0;
int child_flex = box_child.flex;
if (main_free_space.is_bounded() && child_flex > 0) {
current_flex += child_flex;
int quot = (main_free_space.value() * current_flex) / flex_sum;
int rem = (main_free_space.value() * current_flex) % flex_sum;
current_padding = quot - total_padding;
// Use the current remainder to round to the nearest pixel.
if (std::abs(rem) * 2 >= flex_sum) {
current_padding += main_free_space > 0 ? 1 : -1;
}
total_padding += current_padding;
}
int child_min_size = GetMinimumSizeForView(child_layout.child_view);
if (child_min_size > 0 && !collapse_margins_spacing_) {
child_min_size += box_child.margins.main_leading();
}
box_child.actual_bounds.set_size_main(
std::max(child_min_size,
GetActualMainSizeAndUpdateChildPreferredSizeIfNeeded(
bounds, data, i, current_padding, cross_axis_size)));
if (box_child.actual_bounds.size_main() > 0 || box_child.flex > 0) {
data.total_size.set_main(box_child.actual_bounds.max_main());
if (i < num_child - 1) {
if (collapse_margins_spacing_) {
data.total_size.Enlarge(
std::max(between_child_spacing_, child_margins.main_trailing()),
0);
} else {
data.total_size.Enlarge(between_child_spacing_, 0);
}
}
if (!collapse_margins_spacing_) {
data.total_size.Enlarge(child_margins.main_trailing(), 0);
}
}
int cross_size = box_child.preferred_size.cross();
if (cross_axis_alignment_ == CrossAxisAlignment::kStart) {
cross_size +=
data.max_cross_margin.leading() + child_margins.cross_trailing();
} else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
cross_size +=
data.max_cross_margin.trailing() + child_margins.cross_leading();
} else {
int view_center = box_child.preferred_size.cross() / 2;
// When center aligning, if the size is an odd number, we want the view to
// be to the left instead of to the right.
if (cross_axis_alignment_ == CrossAxisAlignment::kCenter) {
view_center += box_child.preferred_size.cross() & 1;
}
int old_cross_center_pos = data.cross_center_pos;
data.cross_center_pos = std::max(
data.cross_center_pos, child_margins.cross_leading() + view_center);
cross_size = data.cross_center_pos + box_child.preferred_size.cross() -
view_center + child_margins.cross_trailing();
// If the new center point has moved to the right relative to the original
// center point, then we need to move all the views to the right, so the
// original total size increases by |data.cross_center_pos -
// old_cross_center_pos|.
data.total_size.Enlarge(
0, std::max(0, data.cross_center_pos - old_cross_center_pos));
}
data.total_size.set_cross(std::max(data.total_size.cross(), cross_size));
}
EnsureCrossSize(data);
const SizeBound cross_total_size =
bounds.cross().is_bounded() && bounds.cross().value() > 0
? bounds.cross()
: data.total_size.cross();
if (cross_axis_alignment_ == CrossAxisAlignment::kCenter) {
const int center_offset =
std::floor((cross_total_size.value() - data.total_size.cross()) / 2.0);
for (BoxChildData& child : data.child_data) {
const SizeBound cross_axis_size =
cross_total_size - child.margins.cross_size();
child.actual_bounds.set_size_cross(
std::min(cross_axis_size.value(), child.preferred_size.cross()));
int view_center = child.actual_bounds.size_cross() / 2 +
(child.actual_bounds.size_cross() & 1);
child.actual_bounds.set_origin_cross(
std::max(0, data.cross_center_pos - view_center + center_offset));
}
} else {
for (BoxChildData& child : data.child_data) {
const SizeBound cross_axis_size =
cross_total_size - child.margins.cross_size();
Span container(child.margins.cross_leading(), cross_axis_size.value());
Span new_cross(
0, std::min(cross_axis_size.value(), child.preferred_size.cross()));
new_cross.Align(container, cross_axis_alignment_);
child.actual_bounds.set_origin_cross(new_cross.start());
child.actual_bounds.set_size_cross(new_cross.length());
}
}
// Flex views should have grown/shrunk to consume all free space.
if (flex_sum && main_free_space.is_bounded()) {
DCHECK_EQ(total_padding, main_free_space);
}
}
int BoxLayout::GetActualMainSizeAndUpdateChildPreferredSizeIfNeeded(
const NormalizedSizeBounds& bounds,
BoxLayoutData& data,
size_t index,
int current_padding,
SizeBound cross_axis_size) const {
BoxChildData& box_child = data.child_data[index];
ChildLayout& child_layout = data.layout.child_layouts[index];
SizeBound avaible_main_size =
std::max<SizeBound>(0, bounds.main() - data.total_size.main());
// Label is a special view that can be shrunk even when its flex value is 0.
// When the flex value is 0, current_padding must be 0, because
// current_padding is the extra available space distributed to
// subviews proportional to their flex values. But if there is
// insufficient space at this time. We should need shrink.
DCHECK(box_child.flex > 0 || current_padding == 0);
bool need_shrink = current_padding < 0 ||
avaible_main_size < box_child.preferred_size.main();
if (need_shrink) {
avaible_main_size = std::max<SizeBound>(
0, avaible_main_size.min_of(box_child.preferred_size.main() +
current_padding));
// Calculate the preferred size given the current size.
box_child.preferred_size = Normalize(
orientation_,
GetPreferredSizeForView(
child_layout.child_view,
NormalizedSizeBounds(avaible_main_size, cross_axis_size)));
return box_child.flex > 0 ? avaible_main_size.value()
: box_child.preferred_size.main();
} else {
return box_child.preferred_size.main() + current_padding;
}
}
void BoxLayout::CalculateChildBounds(const SizeBounds& size_bounds,
BoxLayoutData& data) const {
// Apply main axis alignment (we've already done cross-axis alignment above).
const NormalizedSizeBounds normalized_bounds =
Normalize(orientation_, size_bounds);
const NormalizedSize normalized_host_size =
Normalize(orientation_, data.layout.host_size);
int available_main = normalized_bounds.main().is_bounded()
? normalized_bounds.main().value()
: normalized_host_size.main();
available_main = std::max(0, available_main - data.host_insets.main_size());
const int excess_main = available_main - data.total_size.main();
NormalizedPoint start(data.host_insets.main_leading(),
data.host_insets.cross_leading());
int flex_sum = std::accumulate(
data.child_data.cbegin(), data.child_data.cend(), 0,
[](int total, const BoxChildData& data) { return total + data.flex; });
// BoxLayoutTest.FlexShrinkHorizontal relies on this behavior, why is
// alignment needed only when there is no flex?
if (!flex_sum) {
switch (main_axis_alignment()) {
case LayoutAlignment::kStart:
break;
case LayoutAlignment::kCenter:
start.set_main(start.main() + excess_main / 2);
break;
case LayoutAlignment::kEnd:
start.set_main(start.main() + excess_main);
break;
case LayoutAlignment::kStretch:
case LayoutAlignment::kBaseline:
NOTIMPLEMENTED();
break;
}
}
// Calculate the actual child bounds.
for (size_t i = 0; i < data.num_children(); ++i) {
ChildLayout& child_layout = data.layout.child_layouts[i];
BoxChildData& box_child = data.child_data[i];
NormalizedRect actual = box_child.actual_bounds;
actual.Offset(start.main(), start.cross());
// If the view exceeds the space, truncate the view.
if (actual.origin_main() < data.host_insets.main_leading()) {
actual.SetByBounds(data.host_insets.main_leading(), actual.origin_cross(),
actual.max_main(), actual.max_cross());
}
if (actual.max_main() > data.host_insets.main_leading() + available_main) {
actual.SetByBounds(actual.origin_main(), actual.origin_cross(),
data.host_insets.main_leading() + available_main,
actual.max_cross());
}
child_layout.bounds = Denormalize(orientation_, actual);
}
}
} // namespace views