910e62b5创建于 1月15日历史提交
// Copyright 2021 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/table_layout.h"

#include <algorithm>
#include <functional>
#include <memory>
#include <numeric>
#include <optional>
#include <utility>

#include "base/check_op.h"
#include "base/containers/span.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"

namespace views {

namespace {

// A LayoutElement has a size and location along one axis. It contains methods
// that are used along both axes.
class LayoutElement {
 public:
  explicit LayoutElement(float resize) : resize_(resize) {
    DCHECK_GE(resize, 0) << "Can't give a row or column negative resize";
  }
  LayoutElement(const LayoutElement&) = default;
  LayoutElement(LayoutElement&&) = default;
  LayoutElement& operator=(const LayoutElement&) = default;
  LayoutElement& operator=(LayoutElement&&) = default;
  virtual ~LayoutElement() = default;

  // Sets the size of this element given a preferred `size`.
  virtual void AdjustSize(int size) { size_ = std::max(size_, size); }

  // Resets the size to the initial size.
  virtual void ResetSize() = 0;

  // Sets the location of each element to be the sum of the sizes of the
  // preceding elements.
  template <class T>
  static void CalculateLocationsFromSize(std::vector<T>& elements) {
    int location = 0;
    for (auto& element : elements) {
      element.location_ = location;
      location += element.size();
    }
  }

  float resize() const { return resize_; }
  bool resizable() const { return resize_ > 0; }
  int location() const { return location_; }
  void set_size(int size) { size_ = size; }
  int size() const { return size_; }

 private:
  float resize_;
  int location_ = 0;
  int size_ = 0;
};

// Invokes ResetSize on all the layout elements.
template <class T>
void ResetSizes(std::vector<T>& elements) {
  for (auto& element : elements) {
    element.ResetSize();
  }
}

// Distributes delta among the resizable elements. Each resizable element is
// given (resize() / total_resize * delta) DIP of extra space.
template <class T>
void DistributeDelta(int delta, std::vector<T>& elements) {
  if (delta == 0) {
    return;
  }

  float total_resize = 0;
  int resize_count = 0;
  for (auto& element : elements) {
    total_resize += element.resize();
    if (element.resize() > 0) {
      ++resize_count;
    }
  }
  if (total_resize == 0) {
    return;
  }
  int remaining_delta = delta;
  for (auto& element : elements) {
    if (element.resize() > 0) {
      int element_delta = remaining_delta;
      if (--resize_count != 0) {
        element_delta =
            base::ClampFloor(delta * (element.resize() / total_resize));
        remaining_delta -= element_delta;
      }
      element.set_size(element.size() + element_delta);
    }
  }
}

// Returns the sum of the size of the elements from `start` to
// `start` + `length`.
template <class T>
int TotalSize(size_t start, size_t length, const std::vector<T>& elements) {
  DCHECK_GT(length, 0u);
  DCHECK_LE(start + length, elements.size());
  const auto begin = elements.cbegin() + static_cast<ptrdiff_t>(start);
  return std::accumulate(
      begin, begin + static_cast<ptrdiff_t>(length), 0,
      [](int size, const auto& elem) { return size + elem.size(); });
}

// Advances `index` past any padding elements.
template <class T>
void SkipPadding(size_t& index, const std::vector<T>& elements) {
  while (index < elements.size() && elements[index].is_padding()) {
    ++index;
  }
}

void CalculateLocationAndSize(int pref_size,
                              LayoutAlignment alignment,
                              int* location,
                              int* size) {
  if (alignment != LayoutAlignment::kStretch) {
    int available_size = *size;
    *size = std::min(*size, pref_size);
    switch (alignment) {
      case LayoutAlignment::kStart:
        // Nothing to do, location already points to start.
        break;
      case LayoutAlignment::kBaseline:  // If we were asked to align on
                                        // baseline, but the view doesn't have a
                                        // baseline, fall back to center.
      case LayoutAlignment::kCenter:
        *location += (available_size - *size) / 2;
        break;
      case LayoutAlignment::kEnd:
        *location = *location + available_size - *size;
        break;
      default:
        NOTREACHED();
    }
  }
}

}  // namespace

constexpr float TableLayout::kFixedSize;

// As the name implies, this represents a Column. Column contains default values
// for views originating in this column.
class TableLayout::Column : public LayoutElement {
 public:
  Column(LayoutAlignment h_align,
         LayoutAlignment v_align,
         float horizontal_resize,
         ColumnSize size_type,
         int fixed_width,
         int min_width,
         bool is_padding)
      : LayoutElement(horizontal_resize),
        h_align_(h_align),
        v_align_(v_align),
        size_type_(size_type),
        fixed_width_(fixed_width),
        min_width_(min_width),
        is_padding_(is_padding) {}
  Column(const Column&) = default;
  Column(Column&&) = default;
  Column& operator=(const Column&) = default;
  Column& operator=(Column&&) = default;
  ~Column() override = default;

  void AdjustSize(int size) override {
    if (size_type_ == ColumnSize::kUsePreferred) {
      LayoutElement::AdjustSize(size);
    }
  }

  void ResetSize() override {
    set_size((size_type_ == ColumnSize::kFixed) ? fixed_width_ : min_width_);
  }

  // Determines the max size of all linked columns, and sets each column to that
  // size.
  void UnifyLinkedColumnSizes(const std::optional<int>& size_limit) {
    if (linked_columns_.empty() || linked_columns_.front() != this) {
      return;
    }

    // Accumulate the size first.
    int size = 0;
    for (views::TableLayout::Column* column : linked_columns_) {
      if (!size_limit || column->size() <= *size_limit) {
        size = std::max(size, column->size());
      }
    }

    // Then apply it.
    for (views::TableLayout::Column* column : linked_columns_) {
      column->set_size(std::max(size, column->size()));
    }
  }

  void set_linked_columns(
      const std::vector<raw_ptr<Column, VectorExperimental>>& linked_columns) {
    DCHECK(linked_columns_.empty()) << "Cannot link a column twice";
    linked_columns_ = linked_columns;
  }

  LayoutAlignment h_align() const { return h_align_; }
  LayoutAlignment v_align() const { return v_align_; }
  ColumnSize size_type() const { return size_type_; }
  int min_width() const { return min_width_; }
  bool is_padding() const { return is_padding_; }

 private:
  LayoutAlignment h_align_;
  LayoutAlignment v_align_;
  ColumnSize size_type_;
  int fixed_width_;
  int min_width_;
  bool is_padding_;
  std::vector<raw_ptr<Column, VectorExperimental>> linked_columns_;
};

class TableLayout::Row : public LayoutElement {
 public:
  Row(float vertical_resize, int height, bool is_padding)
      : LayoutElement(vertical_resize),
        height_(height),
        is_padding_(is_padding) {}
  Row(const Row&) = default;
  Row(Row&&) = default;
  Row& operator=(const Row&) = default;
  Row& operator=(Row&&) = default;
  ~Row() override = default;

  void ResetSize() override {
    max_ascent_ = max_descent_ = 0;
    set_size(height_);
  }

  // Adjusts the size to accommodate the specified `ascent`/`descent`.
  void AdjustSizeForBaseline(int ascent, int descent) {
    max_ascent_ = std::max(ascent, max_ascent_);
    max_descent_ = std::max(descent, max_descent_);
    AdjustSize(max_ascent_ + max_descent_);
  }

  bool is_padding() const { return is_padding_; }
  int max_ascent() const { return max_ascent_; }

 private:
  int height_;
  bool is_padding_;
  int max_ascent_ = 0;
  int max_descent_ = 0;
};

// Identifies the location in the grid of a particular view, along with
// placement information and size information.
struct TableLayout::ViewState {
  ViewState() = default;
  ViewState(View* view,
            size_t start_col,
            size_t start_row,
            size_t col_span,
            size_t row_span,
            LayoutAlignment h_align,
            LayoutAlignment v_align)
      : view(view),
        start_col(start_col),
        start_row(start_row),
        col_span(col_span),
        row_span(row_span),
        h_align(h_align),
        v_align(v_align) {
    DCHECK(view);
    DCHECK_GT(col_span, 0u);
    DCHECK_GT(row_span, 0u);
  }

  raw_ptr<View, DanglingUntriaged> view = nullptr;
  size_t start_col = 0;
  size_t start_row = 0;
  size_t col_span = 0;
  size_t row_span = 0;
  LayoutAlignment h_align = LayoutAlignment::kStart;
  LayoutAlignment v_align = LayoutAlignment::kStart;

  // The preferred size, only set during the preferred size pass
  // (SizeCalculationType::kPreferred).
  gfx::Size pref_size;

  // The width/height. This is either the preferred width or the minimum width
  // depending on the pass.
  int width = 0;
  int height = 0;

  // Used during layout. Gives how much width/height has not yet been
  // distributed to the columns/rows the view is in.
  int remaining_width = 0;
  int remaining_height = 0;

  // The baseline. Only used if the view is vertically aligned along the
  // baseline.
  std::optional<int> baseline;
};

TableLayout::TableLayout() = default;

TableLayout::~TableLayout() = default;

size_t TableLayout::NumColumns() const {
  return columns_.size();
}

size_t TableLayout::NumRows() const {
  return rows_.size();
}

TableLayout& TableLayout::AddColumn(LayoutAlignment h_align,
                                    LayoutAlignment v_align,
                                    float horizontal_resize,
                                    ColumnSize size_type,
                                    int fixed_width,
                                    int min_width) {
  columns_.emplace_back(h_align, v_align, horizontal_resize, size_type,
                        fixed_width, min_width, false);
  return *this;
}

TableLayout& TableLayout::AddPaddingColumn(float horizontal_resize, int width) {
  columns_.emplace_back(LayoutAlignment::kStretch, LayoutAlignment::kStretch,
                        horizontal_resize, ColumnSize::kFixed, width, width,
                        true);
  return *this;
}

void TableLayout::RemoveColumns(size_t n) {
  CHECK_LE(n, columns_.size());
  columns_.erase(columns_.end() - static_cast<ptrdiff_t>(n), columns_.end());
}

TableLayout& TableLayout::AddRows(size_t n, float vertical_resize, int height) {
  for (size_t i = 0; i < n; ++i) {
    rows_.emplace_back(vertical_resize, height, false);
  }
  return *this;
}

TableLayout& TableLayout::AddPaddingRow(float vertical_resize, int height) {
  rows_.emplace_back(vertical_resize, height, true);
  return *this;
}

void TableLayout::RemoveRows(size_t n) {
  CHECK_LE(n, rows_.size());
  rows_.erase(rows_.end() - static_cast<ptrdiff_t>(n), rows_.end());
}

TableLayout& TableLayout::LinkColumnSizes(std::vector<size_t> columns) {
  if (columns.size() > 1) {
    std::ranges::sort(columns);
    DCHECK_LT(columns.back(), columns_.size())
        << "Cannot link an unspecified column";

    std::vector<raw_ptr<Column, VectorExperimental>> linked_columns;
    std::ranges::transform(columns, std::back_inserter(linked_columns),
                           [&](size_t index) { return &columns_[index]; });

    for (views::TableLayout::Column* column : linked_columns) {
      column->set_linked_columns(linked_columns);
    }
  }

  return *this;
}

TableLayout& TableLayout::SetLinkedColumnSizeLimit(int size_limit) {
  linked_column_size_limit_ = size_limit;
  OnLayoutChanged();
  return *this;
}

TableLayout& TableLayout::SetMinimumSize(const gfx::Size& size) {
  minimum_size_ = size;
  OnLayoutChanged();
  return *this;
}

ProposedLayout TableLayout::CalculateProposedLayout(
    const SizeBounds& size_bounds) const {
  ProposedLayout layout;
  layout.host_size = SizeRowsAndColumns(size_bounds);
  layout.host_size.SetToMax(minimum_size_);

  for (View* child : GetChildViewsInPaintOrder(host_view())) {
    if (!child->GetProperty(kViewIgnoredByLayoutKey)) {
      layout.child_layouts.push_back({child, true, {}, {}});
    }
  }

  // Size each view.
  for (const auto& view_state : view_states_by_row_span_) {
    View* view = view_state->view;
    DCHECK(view);
    const gfx::Insets& insets = host_view()->GetInsets();
    int x = columns_[view_state->start_col].location() + insets.left();
    int width =
        TotalSize(view_state->start_col, view_state->col_span, columns_);
    CalculateLocationAndSize(view_state->width, view_state->h_align, &x,
                             &width);
    int y = rows_[view_state->start_row].location() + insets.top();
    int height = TotalSize(view_state->start_row, view_state->row_span, rows_);
    if (view_state->v_align == LayoutAlignment::kBaseline &&
        view_state->baseline) {
      y += rows_[view_state->start_row].max_ascent() - *view_state->baseline;
      height = view_state->height;
    } else {
      CalculateLocationAndSize(view_state->height, view_state->v_align, &y,
                               &height);
    }

    auto it =
        std::ranges::find(layout.child_layouts, view, &ChildLayout::child_view);
    DCHECK(it != layout.child_layouts.cend());
    it->bounds = gfx::Rect(x, y, width, height);
    it->available_size = SizeBounds(width, height);
  }

  return layout;
}

void TableLayout::SetViewStates() const {
  view_states_by_row_span_.clear();
  view_states_by_col_span_.clear();

  size_t col = 0, row = 0;
  std::vector<ViewState*> row_spans;
  for (View* child : GetChildViewsInPaintOrder(host_view())) {
    if (!IsChildIncludedInLayout(child)) {
      continue;
    }

    // Move (col, row) to next open cell.
    for (; row < rows_.size(); ++row) {
      SkipPadding(row, rows_);
      SkipPadding(col, columns_);
      for (auto it = row_spans.begin(); it != row_spans.end();) {
        if (col < (*it)->start_col) {
          break;
        }
        const size_t last_row_of_span = (*it)->start_row + (*it)->row_span - 1;
        if (row <= last_row_of_span) {
          col = std::max(col, (*it)->start_col + (*it)->col_span);
        }
        if (row >= last_row_of_span) {
          it = row_spans.erase(it);
        } else {
          ++it;
        }
        SkipPadding(col, columns_);
      }
      if (col < columns_.size()) {
        break;
      }
      col = 0;
    }
    CHECK_LT(row, rows_.size())
        << "There're not enough cells for layout. Did you forget to "
           "call AddRows()?";

    // Construct a ViewState for this `child`.
    const gfx::Size* span = child->GetProperty(kTableColAndRowSpanKey);
    const size_t col_span = span ? static_cast<size_t>(span->width()) : 1;
    const size_t row_span = span ? static_cast<size_t>(span->height()) : 1;
    LayoutAlignment* const child_h_align =
        child->GetProperty(kTableHorizAlignKey);
    const LayoutAlignment h_align =
        child_h_align ? *child_h_align : columns_[col].h_align();
    LayoutAlignment* const child_v_align =
        child->GetProperty(kTableVertAlignKey);
    const LayoutAlignment v_align =
        child_v_align ? *child_v_align : columns_[col].v_align();
    auto view_state = std::make_unique<ViewState>(child, col, row, col_span,
                                                  row_span, h_align, v_align);

    // Add `view_state` to the relevant vectors.
    ViewState* ptr;
    {
      auto it = std::ranges::lower_bound(view_states_by_row_span_,
                                         view_state->row_span, std::less<>(),
                                         &ViewState::row_span);
      ptr = view_states_by_row_span_.insert(it, std::move(view_state))->get();
    }
    {
      auto it =
          std::ranges::lower_bound(view_states_by_col_span_, ptr->col_span,
                                   std::less<>(), &ViewState::col_span);
      view_states_by_col_span_.insert(it, ptr);
    }
    if (ptr->row_span > 1) {
      DCHECK_LE(row + ptr->row_span, rows_.size())
          << "row_span extends past trailing edge";
      auto it = std::ranges::lower_bound(row_spans, ptr->start_col,
                                         std::less<>(), &ViewState::start_col);
      row_spans.insert(it, ptr);
    }

    // Move past the end of this child, to prepare for the next loop iteration.
    col += ptr->col_span;
    DCHECK_LE(col, columns_.size()) << "col_span extends past trailing edge";
  }
}

gfx::Size TableLayout::SizeRowsAndColumns(const SizeBounds& bounds) const {
  SetViewStates();

  gfx::Size pref;
  if (rows_.empty()) {
    return pref;
  }

  // Calculate the preferred width of each of the columns. Some views'
  // preferred heights are derived from their width, as such we need to
  // calculate the size of the columns first.
  CalculateSize(SizeCalculationType::kPreferred, view_states_by_col_span_);
  const gfx::Insets& insets = host_view()->GetInsets();
  pref.set_width(LayoutWidth() + insets.width());

  // Go over the columns again and set them all to the size we settled for.
  const int bounded_width =
      bounds.width().is_bounded() ? bounds.width().value() : pref.width();
  Resize(bounded_width - pref.width());
  LayoutElement::CalculateLocationsFromSize(columns_);

  // Reset the height of each row.
  ResetSizes(rows_);

  for (auto& view_state : view_states_by_row_span_) {
    if (view_state->v_align == LayoutAlignment::kBaseline) {
      view_state->baseline = view_state->view->GetBaseline();
    }

    // If the view is given a different width than its preferred width, requery
    // for the preferred height. This is necessary as the preferred height may
    // depend upon the width.
    int actual_width =
        TotalSize(view_state->start_col, view_state->col_span, columns_);
    int x = 0;  // Not used in this stage.
    CalculateLocationAndSize(view_state->width, view_state->h_align, &x,
                             &actual_width);
    if (actual_width != view_state->width) {
      view_state->height = view_state->view->GetHeightForWidth(actual_width);
    }

    view_state->remaining_height = view_state->height;
  }

  // Update the height/ascent/descent of each row from the views.
  auto view_states_iterator = view_states_by_row_span_.begin();
  for (; view_states_iterator != view_states_by_row_span_.end() &&
         (*view_states_iterator)->row_span == 1;
       ++view_states_iterator) {
    auto& view_state = *view_states_iterator;
    Row& row = rows_[view_state->start_row];
    row.AdjustSize(view_state->remaining_height);
    if (view_state->baseline.has_value() &&
        *view_state->baseline <= view_state->height) {
      row.AdjustSizeForBaseline(*view_state->baseline,
                                view_state->height - *view_state->baseline);
    }
    view_state->remaining_height = 0;
  }

  // Distribute the height of each view with a row_span > 1.
  for (; view_states_iterator != view_states_by_row_span_.end();
       ++view_states_iterator) {
    auto& view_state = *view_states_iterator;
    view_state->remaining_height -=
        TotalSize(view_state->start_row, view_state->row_span, rows_);
    DistributeRemainingHeight(*view_state);
  }

  // Update the location of each of the rows.
  LayoutElement::CalculateLocationsFromSize(rows_);

  // We now know the preferred height, set it here.
  pref.set_height(rows_.back().location() + rows_.back().size() +
                  insets.height());

  if (bounds.height().is_bounded() && bounds.height() != pref.height()) {
    // Divvy up the extra space.
    DistributeDelta(bounds.height().value() - pref.height(), rows_);

    // Reset y locations.
    LayoutElement::CalculateLocationsFromSize(rows_);
  }

  return pref;
}

void TableLayout::DistributeRemainingHeight(ViewState& view_state) const {
  // Given the set S of rows in (view_state.start_row, view_state.row_span):
  //   If any member of S is resizable,
  //     space is distributed between the resizable members of S
  //   Otherwise, space is distributed between all members of S
  if (view_state.remaining_height <= 0) {
    return;
  }

  // Determine the number of resizable rows the view touches.
  const base::span<Row> rows_to_resize =
      base::span(rows_).subspan(view_state.start_row, view_state.row_span);
  const auto resizable_rows = static_cast<size_t>(
      std::ranges::count_if(rows_to_resize, &Row::resizable));
  size_t remaining_rows =
      resizable_rows ? resizable_rows : rows_to_resize.size();
  for (Row& row : rows_to_resize) {
    if (!resizable_rows || row.resizable()) {
      // We have to recompute the delta each pass through the loop, rather than
      // computing it up front. Although this math appears equivalent to giving
      // each view an equal share of the initial remaining height, if we did do
      // that, we'd end up with a rounding error. Recomputing the delta like
      // this avoids accumulating that rounding error. For example, if we have
      // n=4 rows and h=22 height to distribute:
      //   delta = ClampRound(22 / 4) = 6 -> h = 16, d = 3
      //   delta = ClampRound(16 / 3) = 5 -> h = 11, d = 2
      //   delta = ClampRound(11 / 2) = 6 -> h = 5, d = 1
      //   delta = ClampRound(5 / 1) = 5 -> h = 0, d = 0
      // which is an optimal distribution; if we instead computed the delta
      // upfront as ClampRound(22 / 4) = 5, we'd end up with d = 2 at the end,
      // and have to either leave a rounding error or stick that leftover into
      // the last row.
      const int delta = base::ClampRound(
          static_cast<float>(view_state.remaining_height) / remaining_rows);
      row.set_size(row.size() + delta);
      view_state.remaining_height -= delta;
      --remaining_rows;
    }
  }
}

void TableLayout::UnifyLinkedColumnSizes() const {
  for (auto& column : columns_) {
    column.UnifyLinkedColumnSizes(linked_column_size_limit_);
  }
}

void TableLayout::DistributeRemainingWidth(ViewState& view_state) const {
  // This is nearly the same as DistributeRemainingHeight(), but not identical.
  // Rows have two states: resizable, or not. Columns have three: resizable,
  // kUsePreferred, or not resizable. This results in slightly different
  // handling for distributing unaccounted size.
  int width = view_state.remaining_width;
  if (width <= 0) {
    return;
  }

  // Determine which columns are resizable, and which have a size type of
  // kUsePreferred.
  size_t resizable_columns = 0;
  size_t pref_size_columns = 0;
  size_t start_col = view_state.start_col;
  size_t max_col = view_state.start_col + view_state.col_span;
  float total_resize = 0;
  for (size_t i = start_col; i < max_col; ++i) {
    if (columns_[i].resizable()) {
      total_resize += columns_[i].resize();
      ++resizable_columns;
    } else if (columns_[i].size_type() == ColumnSize::kUsePreferred) {
      ++pref_size_columns;
    }
  }

  if (resizable_columns > 0) {
    // There are resizable columns, give them the remaining width. The extra
    // width is distributed using the resize values of each column.
    int remaining_width = width;
    for (size_t i = start_col, resize_i = 0; i < max_col; ++i) {
      if (columns_[i].resizable()) {
        ++resize_i;
        const int column_delta =
            (resize_i == resizable_columns)
                ? remaining_width
                : base::ClampFloor(width * columns_[i].resize() / total_resize);
        remaining_width -= column_delta;
        columns_[i].set_size(columns_[i].size() + column_delta);
      }
    }
  } else if (pref_size_columns > 0) {
    // None of the columns are resizable, distribute the width among those
    // that use the preferred size.
    int column_delta = width / static_cast<int>(pref_size_columns);
    for (size_t i = start_col; i < max_col; ++i) {
      if (columns_[i].size_type() == ColumnSize::kUsePreferred) {
        width -= column_delta;
        // If there is slop, we're on the last row; give it all the slop.
        if (width < column_delta) {
          column_delta += width;
        }
        columns_[i].set_size(columns_[i].size() + column_delta);
      }
    }
  }
}

int TableLayout::LayoutWidth() const {
  return std::accumulate(
      columns_.cbegin(), columns_.cend(), 0,
      [](int size, const auto& elem) { return size + elem.size(); });
}

void TableLayout::CalculateSize(
    SizeCalculationType type,
    const std::vector<raw_ptr<ViewState, VectorExperimental>>& view_states)
    const {
  // Reset the size and remaining sizes.
  for (views::TableLayout::ViewState* view_state : view_states) {
    gfx::Size size;
    if (type == SizeCalculationType::kMinimum && CanUseMinimum(*view_state)) {
      // If the min size is bigger than the preferred, use the preferred.
      // This relies on MINIMUM being calculated immediately after PREFERRED,
      // which the rest of this code relies on as well.
      size = view_state->view->GetMinimumSize();
      if (size.width() > view_state->width) {
        size.set_width(view_state->width);
      }
      if (size.height() > view_state->height) {
        size.set_height(view_state->height);
      }
    } else {
      size = view_state->view->GetPreferredSize({/* Unbounded */});
      view_state->pref_size = size;
    }
    view_state->remaining_width = view_state->width = size.width();
    view_state->remaining_height = view_state->height = size.height();
  }

  ResetSizes(columns_);

  // Distribute the size of each view with a col span == 1.
  auto view_state_iterator = view_states.begin();
  for (; view_state_iterator != view_states.end() &&
         (*view_state_iterator)->col_span == 1;
       ++view_state_iterator) {
    ViewState* view_state = *view_state_iterator;
    Column& column = columns_[view_state->start_col];
    column.AdjustSize(view_state->width);
    view_state->remaining_width -= column.size();
  }

  // Make sure all linked columns have the same size.
  UnifyLinkedColumnSizes();

  // Distribute the size of each view with a column span > 1.
  for (; view_state_iterator != view_states.end(); ++view_state_iterator) {
    ViewState* view_state = *view_state_iterator;

    // Update the remaining_width from columns this view_state touches.
    view_state->remaining_width -=
        TotalSize(view_state->start_col, view_state->col_span, columns_);

    // Distribute the remaining width.
    DistributeRemainingWidth(*view_state);

    // Update the size of linked columns.
    // This may need to be combined with previous step.
    UnifyLinkedColumnSizes();
  }
}

void TableLayout::Resize(int delta) const {
  if (delta < 0) {
    // DistributeDelta() assumes resizable columns can equally be shrunk. That
    // isn't desired when given a size smaller than the prefered. Instead the
    // columns need to be resized but bounded by the minimum. ResizeUsingMin()
    // does this.
    ResizeUsingMin(delta);
  } else {
    DistributeDelta(delta, columns_);
  }
}

void TableLayout::ResizeUsingMin(int total_delta) const {
  struct ColumnMinResizeData {
    // The column being resized.
    raw_ptr<Column> column;

    // The remaining amount of space available (the difference between the
    // preferred and minimum).
    int available = 0;

    // How much to shrink the preferred by.
    int delta = 0;
  };

  DCHECK_LE(total_delta, 0);

  // |total_delta| is negative, but easier to do operations when positive.
  total_delta = std::abs(total_delta);

  std::vector<int> preferred_column_sizes(columns_.size());
  for (size_t i = 0; i < columns_.size(); ++i) {
    preferred_column_sizes[i] = columns_[i].size();
  }

  // Recalculate the sizes using the min.  We don't want to touch the proposed
  // widths and heights, so copy the ViewStates to a temporary location so
  // modifications to them aren't reflected in the members.
  const size_t num_states = view_states_by_col_span_.size();
  std::vector<ViewState> view_states(num_states);
  std::vector<raw_ptr<ViewState, VectorExperimental>> view_state_ptrs(
      num_states);
  for (size_t i = 0; i < num_states; ++i) {
    view_states[i] = *view_states_by_col_span_[i];
    view_state_ptrs[i] = &view_states[i];
  }
  CalculateSize(SizeCalculationType::kMinimum, view_state_ptrs);

  // Build up the set of columns that can be shrunk in |resize_data|, this
  // iteration also resets the size of the column back to the preferred size.
  std::vector<ColumnMinResizeData> resize_data;
  float total_resize = 0;
  for (size_t i = 0; i < columns_.size(); ++i) {
    Column& column = columns_[i];
    const int available =
        std::max(0, preferred_column_sizes[i] -
                        std::max(column.min_width(), column.size()));
    DCHECK_GE(available, 0);
    // Set the size back to preferred. We'll reset the size if necessary later.
    column.set_size(preferred_column_sizes[i]);
    if (!column.resizable() || available == 0) {
      continue;
    }
    resize_data.push_back({&column, available, 0});
    total_resize += column.resize();
  }
  if (resize_data.empty()) {
    return;
  }

  // Loop through the columns updating the amount available and the amount to
  // resize. This may take multiple iterations if the column min is hit.
  // Generally there are not that many columns in a table, so this code is
  // not optimized. Any time the column hits the min it is removed from
  // |resize_data|.
  while (!resize_data.empty() && total_delta > 0) {
    float next_iteration_total_resize = total_resize;
    int next_iteration_delta = total_delta;
    for (size_t i = resize_data.size(); i > 0; --i) {
      ColumnMinResizeData& data = resize_data[i - 1];
      int delta = std::min(
          data.available,
          base::ClampFloor(total_delta * data.column->resize() / total_resize));
      // Make sure at least one column is resized (rounding errors may prevent
      // that).
      if (i == 1 && delta == 0 && next_iteration_delta == total_delta) {
        delta = 1;
      }
      next_iteration_delta -= delta;
      data.delta += delta;
      data.available -= delta;
      if (data.available == 0) {
        data.column->set_size(data.column->size() - data.delta);
        next_iteration_total_resize -= data.column->resize();
        resize_data.erase(resize_data.begin() + static_cast<ptrdiff_t>(i - 1));
      }
    }
    DCHECK_LT(next_iteration_delta, total_delta);
    total_delta = next_iteration_delta;
    total_resize = next_iteration_total_resize;
  }

  for (const ColumnMinResizeData& data : resize_data) {
    data.column->set_size(data.column->size() - data.delta);
  }
}

bool TableLayout::CanUseMinimum(const ViewState& view_state) const {
  const auto begin =
      columns_.cbegin() + static_cast<ptrdiff_t>(view_state.start_col);
  return std::any_of(begin, begin + static_cast<ptrdiff_t>(view_state.col_span),
                     [](const auto& col) {
                       return col.resizable() &&
                              col.size_type() != ColumnSize::kFixed;
                     });
}

}  // namespace views