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