summaryrefslogtreecommitdiffstats
path: root/views/layout
diff options
context:
space:
mode:
authortfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-01-27 02:17:21 +0000
committertfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-01-27 02:17:21 +0000
commit9ef7d4bcd2bbd016f92d67c4aea8a45f29c8d3ee (patch)
treeac0ac694511db7a15f3e6440d1bd69af2efd003e /views/layout
parent74c3fb5ff6eec3ce2b9c18aeb0aab42c0db25034 (diff)
downloadchromium_src-9ef7d4bcd2bbd016f92d67c4aea8a45f29c8d3ee.zip
chromium_src-9ef7d4bcd2bbd016f92d67c4aea8a45f29c8d3ee.tar.gz
chromium_src-9ef7d4bcd2bbd016f92d67c4aea8a45f29c8d3ee.tar.bz2
views: Move grid_layout files into layout directory. Final Part.
BUG=None TEST=trybots Review URL: http://codereview.chromium.org/6382009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@72748 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views/layout')
-rw-r--r--views/layout/grid_layout.cc1070
-rw-r--r--views/layout/grid_layout.h371
-rw-r--r--views/layout/grid_layout_unittest.cc608
3 files changed, 2049 insertions, 0 deletions
diff --git a/views/layout/grid_layout.cc b/views/layout/grid_layout.cc
new file mode 100644
index 0000000..e516779
--- /dev/null
+++ b/views/layout/grid_layout.cc
@@ -0,0 +1,1070 @@
+// Copyright (c) 2011 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 "views/layout/grid_layout.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/stl_util-inl.h"
+#include "gfx/insets.h"
+#include "views/layout/layout_constants.h"
+#include "views/view.h"
+
+namespace views {
+
+// LayoutElement ------------------------------------------------------
+
+// A LayoutElement has a size and location along one axis. It contains
+// methods that are used along both axis.
+class LayoutElement {
+ public:
+ // Invokes ResetSize on all the layout elements.
+ template <class T>
+ static void ResetSizes(std::vector<T*>* elements) {
+ // Reset the layout width of each column.
+ for (typename std::vector<T*>::iterator i = elements->begin();
+ i != elements->end(); ++i) {
+ (*i)->ResetSize();
+ }
+ }
+
+ // 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) {
+ // Reset the layout width of each column.
+ int location = 0;
+ for (typename std::vector<T*>::iterator i = elements->begin();
+ i != elements->end(); ++i) {
+ (*i)->SetLocation(location);
+ location += (*i)->Size();
+ }
+ }
+
+ // Distributes delta among the resizable elements.
+ // Each resizable element is given ResizePercent / total_percent * delta
+ // pixels extra of space.
+ template <class T>
+ static void DistributeDelta(int delta, std::vector<T*>* elements) {
+ if (delta == 0)
+ return;
+
+ float total_percent = 0;
+ int resize_count = 0;
+ for (typename std::vector<T*>::iterator i = elements->begin();
+ i != elements->end(); ++i) {
+ total_percent += (*i)->ResizePercent();
+ resize_count++;
+ }
+ if (total_percent == 0) {
+ // None of the elements are resizable, return.
+ return;
+ }
+ int remaining = delta;
+ int resized = resize_count;
+ for (typename std::vector<T*>::iterator i = elements->begin();
+ i != elements->end(); ++i) {
+ T* element = *i;
+ if (element->ResizePercent() > 0) {
+ int to_give;
+ if (--resized == 0) {
+ to_give = remaining;
+ } else {
+ to_give = static_cast<int>(delta *
+ (element->resize_percent_ / total_percent));
+ remaining -= to_give;
+ }
+ element->SetSize(element->Size() + to_give);
+ }
+ }
+ }
+
+ // Returns the sum of the size of the elements from start to start + length.
+ template <class T>
+ static int TotalSize(int start, int length, std::vector<T*>* elements) {
+ DCHECK(start >= 0 && length > 0 &&
+ start + length <= static_cast<int>(elements->size()));
+ int size = 0;
+ for (int i = start, max = start + length; i < max; ++i) {
+ size += (*elements)[i]->Size();
+ }
+ return size;
+ }
+
+ explicit LayoutElement(float resize_percent)
+ : resize_percent_(resize_percent) {
+ DCHECK(resize_percent >= 0);
+ }
+
+ virtual ~LayoutElement() {}
+
+ void SetLocation(int location) {
+ location_ = location;
+ }
+
+ int Location() {
+ return location_;
+ }
+
+ // Adjusts the size of this LayoutElement to be the max of the current size
+ // and the specified size.
+ virtual void AdjustSize(int size) {
+ size_ = std::max(size_, size);
+ }
+
+ // Resets the size to the initial size. This sets the size to 0, but
+ // subclasses that have a different initial size should override.
+ virtual void ResetSize() {
+ SetSize(0);
+ }
+
+ void SetSize(int size) {
+ size_ = size;
+ }
+
+ int Size() {
+ return size_;
+ }
+
+ void SetResizePercent(float percent) {
+ resize_percent_ = percent;
+ }
+
+ float ResizePercent() {
+ return resize_percent_;
+ }
+
+ bool IsResizable() {
+ return resize_percent_ > 0;
+ }
+
+ private:
+ float resize_percent_;
+ int location_;
+ int size_;
+
+ DISALLOW_COPY_AND_ASSIGN(LayoutElement);
+};
+
+// Column -------------------------------------------------------------
+
+// As the name implies, this represents a Column. Column contains default
+// values for views originating in this column.
+class Column : public LayoutElement {
+ public:
+ Column(GridLayout::Alignment h_align,
+ GridLayout::Alignment v_align,
+ float resize_percent,
+ GridLayout::SizeType size_type,
+ int fixed_width,
+ int min_width,
+ size_t index,
+ bool is_padding)
+ : LayoutElement(resize_percent),
+ h_align_(h_align),
+ v_align_(v_align),
+ size_type_(size_type),
+ same_size_column_(-1),
+ fixed_width_(fixed_width),
+ min_width_(min_width),
+ index_(index),
+ is_padding_(is_padding),
+ master_column_(NULL) {}
+
+ virtual ~Column() {}
+
+ GridLayout::Alignment h_align() { return h_align_; }
+ GridLayout::Alignment v_align() { return v_align_; }
+
+ virtual void ResetSize();
+
+ private:
+ friend class ColumnSet;
+ friend class GridLayout;
+
+ Column* GetLastMasterColumn();
+
+ // Determines the max size of all linked columns, and sets each column
+ // to that size. This should only be used for the master column.
+ void UnifySameSizedColumnSizes();
+
+ virtual void AdjustSize(int size);
+
+ const GridLayout::Alignment h_align_;
+ const GridLayout::Alignment v_align_;
+ const GridLayout::SizeType size_type_;
+ int same_size_column_;
+ const int fixed_width_;
+ const int min_width_;
+
+ // Index of this column in the ColumnSet.
+ const size_t index_;
+
+ const bool is_padding_;
+
+ // If multiple columns have their sizes linked, one is the
+ // master column. The master column is identified by the
+ // master_column field being equal to itself. The master columns
+ // same_size_columns field contains the set of Columns with the
+ // the same size. Columns who are linked to other columns, but
+ // are not the master column have their master_column pointing to
+ // one of the other linked columns. Use the method GetLastMasterColumn
+ // to resolve the true master column.
+ std::vector<Column*> same_size_columns_;
+ Column* master_column_;
+
+ DISALLOW_COPY_AND_ASSIGN(Column);
+};
+
+void Column::ResetSize() {
+ if (size_type_ == GridLayout::FIXED) {
+ SetSize(fixed_width_);
+ } else {
+ SetSize(min_width_);
+ }
+}
+
+Column* Column::GetLastMasterColumn() {
+ if (master_column_ == NULL) {
+ return NULL;
+ }
+ if (master_column_ == this) {
+ return this;
+ }
+ return master_column_->GetLastMasterColumn();
+}
+
+void Column::UnifySameSizedColumnSizes() {
+ DCHECK(master_column_ == this);
+
+ // Accumulate the size first.
+ int size = 0;
+ for (std::vector<Column*>::iterator i = same_size_columns_.begin();
+ i != same_size_columns_.end(); ++i) {
+ size = std::max(size, (*i)->Size());
+ }
+
+ // Then apply it.
+ for (std::vector<Column*>::iterator i = same_size_columns_.begin();
+ i != same_size_columns_.end(); ++i) {
+ (*i)->SetSize(size);
+ }
+}
+
+void Column::AdjustSize(int size) {
+ if (size_type_ == GridLayout::USE_PREF)
+ LayoutElement::AdjustSize(size);
+}
+
+// Row -------------------------------------------------------------
+
+class Row : public LayoutElement {
+ public:
+ Row(bool fixed_height, int height, float resize_percent,
+ ColumnSet* column_set)
+ : LayoutElement(resize_percent),
+ fixed_height_(fixed_height),
+ height_(height),
+ column_set_(column_set),
+ max_ascent_(0),
+ max_descent_(0) {
+ }
+
+ virtual ~Row() {}
+
+ virtual void ResetSize() {
+ max_ascent_ = max_descent_ = 0;
+ SetSize(height_);
+ }
+
+ ColumnSet* column_set() {
+ return column_set_;
+ }
+
+ // Adjusts the size to accomodate 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_);
+ }
+
+ int max_ascent() const {
+ return max_ascent_;
+ }
+
+ int max_descent() const {
+ return max_descent_;
+ }
+
+ private:
+ const bool fixed_height_;
+ const int height_;
+ // The column set used for this row; null for padding rows.
+ ColumnSet* column_set_;
+
+ int max_ascent_;
+ int max_descent_;
+
+ DISALLOW_COPY_AND_ASSIGN(Row);
+};
+
+// ViewState -------------------------------------------------------------
+
+// Identifies the location in the grid of a particular view, along with
+// placement information and size information.
+struct ViewState {
+ ViewState(ColumnSet* column_set, View* view, int start_col, int start_row,
+ int col_span, int row_span, GridLayout::Alignment h_align,
+ GridLayout::Alignment v_align, int pref_width, int pref_height)
+ : column_set(column_set),
+ 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),
+ pref_width_fixed(pref_width > 0),
+ pref_height_fixed(pref_height > 0),
+ pref_width(pref_width),
+ pref_height(pref_height),
+ remaining_width(0),
+ remaining_height(0),
+ baseline(-1) {
+ DCHECK(view && start_col >= 0 && start_row >= 0 && col_span > 0 &&
+ row_span > 0 && start_col < column_set->num_columns() &&
+ (start_col + col_span) <= column_set->num_columns());
+ }
+
+ ColumnSet* const column_set;
+ View* const view;
+ const int start_col;
+ const int start_row;
+ const int col_span;
+ const int row_span;
+ const GridLayout::Alignment h_align;
+ const GridLayout::Alignment v_align;
+
+ // If true, the pref_width/pref_height were explicitly set and the view's
+ // preferred size is ignored.
+ const bool pref_width_fixed;
+ const bool pref_height_fixed;
+
+ // The preferred width/height. These are reset during the layout process.
+ int pref_width;
+ int pref_height;
+
+ // Used during layout. Gives how much width/height has not yet been
+ // distributed to the columns/rows the view is in.
+ int remaining_width;
+ int remaining_height;
+
+ // The baseline. Only used if the view is vertically aligned along the
+ // baseline.
+ int baseline;
+};
+
+static bool CompareByColumnSpan(const ViewState* v1, const ViewState* v2) {
+ return v1->col_span < v2->col_span;
+}
+
+static bool CompareByRowSpan(const ViewState* v1, const ViewState* v2) {
+ return v1->row_span < v2->row_span;
+}
+
+// ColumnSet -------------------------------------------------------------
+
+ColumnSet::ColumnSet(int id) : id_(id) {
+}
+
+ColumnSet::~ColumnSet() {
+ STLDeleteElements(&columns_);
+}
+
+void ColumnSet::AddPaddingColumn(float resize_percent, int width) {
+ AddColumn(GridLayout::FILL, GridLayout::FILL, resize_percent,
+ GridLayout::FIXED, width, width, true);
+}
+
+void ColumnSet::AddColumn(GridLayout::Alignment h_align,
+ GridLayout::Alignment v_align,
+ float resize_percent,
+ GridLayout::SizeType size_type,
+ int fixed_width,
+ int min_width) {
+ AddColumn(h_align, v_align, resize_percent, size_type, fixed_width,
+ min_width, false);
+}
+
+
+void ColumnSet::LinkColumnSizes(int first, ...) {
+ va_list marker;
+ va_start(marker, first);
+ DCHECK(first >= 0 && first < num_columns());
+ for (int last = first, next = va_arg(marker, int); next != -1;
+ next = va_arg(marker, int)) {
+ DCHECK(next >= 0 && next < num_columns());
+ columns_[last]->same_size_column_ = next;
+ last = next;
+ }
+ va_end(marker);
+}
+
+void ColumnSet::AddColumn(GridLayout::Alignment h_align,
+ GridLayout::Alignment v_align,
+ float resize_percent,
+ GridLayout::SizeType size_type,
+ int fixed_width,
+ int min_width,
+ bool is_padding) {
+ Column* column = new Column(h_align, v_align, resize_percent, size_type,
+ fixed_width, min_width, columns_.size(),
+ is_padding);
+ columns_.push_back(column);
+}
+
+void ColumnSet::AddViewState(ViewState* view_state) {
+ // view_states are ordered by column_span (in ascending order).
+ std::vector<ViewState*>::iterator i = lower_bound(view_states_.begin(),
+ view_states_.end(),
+ view_state,
+ CompareByColumnSpan);
+ view_states_.insert(i, view_state);
+}
+
+void ColumnSet::CalculateMasterColumns() {
+ for (std::vector<Column*>::iterator i = columns_.begin();
+ i != columns_.end(); ++i) {
+ Column* column = *i;
+ int same_size_column_index = column->same_size_column_;
+ if (same_size_column_index != -1) {
+ DCHECK(same_size_column_index >= 0 &&
+ same_size_column_index < static_cast<int>(columns_.size()));
+ Column* master_column = column->master_column_;
+ Column* same_size_column = columns_[same_size_column_index];
+ Column* same_size_column_master = same_size_column->master_column_;
+ if (master_column == NULL) {
+ // Current column is not linked to any other column.
+ if (same_size_column_master == NULL) {
+ // Both columns are not linked.
+ column->master_column_ = column;
+ same_size_column->master_column_ = column;
+ column->same_size_columns_.push_back(same_size_column);
+ column->same_size_columns_.push_back(column);
+ } else {
+ // Column to link to is linked with other columns.
+ // Add current column to list of linked columns in other columns
+ // master column.
+ same_size_column->GetLastMasterColumn()->
+ same_size_columns_.push_back(column);
+ // And update the master column for the current column to that
+ // of the same sized column.
+ column->master_column_ = same_size_column;
+ }
+ } else {
+ // Current column is already linked with another column.
+ if (same_size_column_master == NULL) {
+ // Column to link with is not linked to any other columns.
+ // Update it's master_column.
+ same_size_column->master_column_ = column;
+ // Add linked column to list of linked column.
+ column->GetLastMasterColumn()->same_size_columns_.
+ push_back(same_size_column);
+ } else if (column->GetLastMasterColumn() !=
+ same_size_column->GetLastMasterColumn()) {
+ // The two columns are already linked with other columns.
+ std::vector<Column*>* same_size_columns =
+ &(column->GetLastMasterColumn()->same_size_columns_);
+ std::vector<Column*>* other_same_size_columns =
+ &(same_size_column->GetLastMasterColumn()->same_size_columns_);
+ // Add all the columns from the others master to current columns
+ // master.
+ same_size_columns->insert(same_size_columns->end(),
+ other_same_size_columns->begin(),
+ other_same_size_columns->end());
+ // The other master is no longer a master, clear its vector of
+ // linked columns, and reset its master_column.
+ other_same_size_columns->clear();
+ same_size_column->GetLastMasterColumn()->master_column_ = column;
+ }
+ }
+ }
+ }
+ AccumulateMasterColumns();
+}
+
+void ColumnSet::AccumulateMasterColumns() {
+ DCHECK(master_columns_.empty());
+ for (std::vector<Column*>::iterator i = columns_.begin();
+ i != columns_.end(); ++i) {
+ Column* column = *i;
+ Column* master_column = column->GetLastMasterColumn();
+ if (master_column &&
+ find(master_columns_.begin(), master_columns_.end(),
+ master_column) == master_columns_.end()) {
+ master_columns_.push_back(master_column);
+ }
+ // At this point, GetLastMasterColumn may not == master_column
+ // (may have to go through a few Columns)_. Reset master_column to
+ // avoid hops.
+ column->master_column_ = master_column;
+ }
+}
+
+void ColumnSet::UnifySameSizedColumnSizes() {
+ for (std::vector<Column*>::iterator i = master_columns_.begin();
+ i != master_columns_.end(); ++i) {
+ (*i)->UnifySameSizedColumnSizes();
+ }
+}
+
+void ColumnSet::UpdateRemainingWidth(ViewState* view_state) {
+ for (int i = view_state->start_col; i < view_state->col_span; ++i) {
+ view_state->remaining_width -= columns_[i]->Size();
+ }
+}
+
+void ColumnSet::DistributeRemainingWidth(ViewState* view_state) {
+ // This is nearly the same as that for rows, but differs in so far as how
+ // Rows and Columns are treated. Rows have two states, resizable or not.
+ // Columns have three, resizable, USE_PREF or not resizable. This results
+ // in slightly different handling for distributing unaccounted size.
+ int width = view_state->remaining_width;
+ if (width <= 0) {
+ // The columns this view is in are big enough to accommodate it.
+ return;
+ }
+
+ // Determine which columns are resizable, and which have a size type
+ // of USE_PREF.
+ int resizable_columns = 0;
+ int pref_size_columns = 0;
+ int start_col = view_state->start_col;
+ int max_col = view_state->start_col + view_state->col_span;
+ float total_resize = 0;
+ for (int i = start_col; i < max_col; ++i) {
+ if (columns_[i]->IsResizable()) {
+ total_resize += columns_[i]->ResizePercent();
+ resizable_columns++;
+ } else if (columns_[i]->size_type_ == GridLayout::USE_PREF) {
+ 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 (int i = start_col, resize_i = 0; i < max_col; ++i) {
+ if (columns_[i]->IsResizable()) {
+ resize_i++;
+ int delta = (resize_i == resizable_columns) ? remaining_width :
+ static_cast<int>(width * columns_[i]->ResizePercent() /
+ total_resize);
+ remaining_width -= delta;
+ columns_[i]->SetSize(columns_[i]->Size() + delta);
+ }
+ }
+ } else if (pref_size_columns > 0) {
+ // None of the columns are resizable, distribute the width among those
+ // that use the preferred size.
+ int to_distribute = width / pref_size_columns;
+ for (int i = start_col; i < max_col; ++i) {
+ if (columns_[i]->size_type_ == GridLayout::USE_PREF) {
+ width -= to_distribute;
+ if (width < to_distribute)
+ to_distribute += width;
+ columns_[i]->SetSize(columns_[i]->Size() + to_distribute);
+ }
+ }
+ }
+}
+
+int ColumnSet::LayoutWidth() {
+ int width = 0;
+ for (std::vector<Column*>::iterator i = columns_.begin();
+ i != columns_.end(); ++i) {
+ width += (*i)->Size();
+ }
+ return width;
+}
+
+int ColumnSet::GetColumnWidth(int start_col, int col_span) {
+ return LayoutElement::TotalSize(start_col, col_span, &columns_);
+}
+
+void ColumnSet::ResetColumnXCoordinates() {
+ LayoutElement::CalculateLocationsFromSize(&columns_);
+}
+
+void ColumnSet::CalculateSize() {
+ gfx::Size pref;
+ // Reset the preferred and remaining sizes.
+ for (std::vector<ViewState*>::iterator i = view_states_.begin();
+ i != view_states_.end(); ++i) {
+ ViewState* view_state = *i;
+ if (!view_state->pref_width_fixed || !view_state->pref_height_fixed) {
+ pref = view_state->view->GetPreferredSize();
+ if (!view_state->pref_width_fixed)
+ view_state->pref_width = pref.width();
+ if (!view_state->pref_height_fixed)
+ view_state->pref_height = pref.height();
+ }
+ view_state->remaining_width = pref.width();
+ view_state->remaining_height = pref.height();
+ }
+
+ // Let layout element reset the sizes for us.
+ LayoutElement::ResetSizes(&columns_);
+
+ // Distribute the size of each view with a col span == 1.
+ std::vector<ViewState*>::iterator 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->pref_width);
+ view_state->remaining_width -= column->Size();
+ }
+
+ // Make sure all linked columns have the same size.
+ UnifySameSizedColumnSizes();
+
+ // 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.
+ UpdateRemainingWidth(view_state);
+
+ // Distribute the remaining width.
+ DistributeRemainingWidth(view_state);
+
+ // Update the size of linked columns.
+ // This may need to be combined with previous step.
+ UnifySameSizedColumnSizes();
+ }
+}
+
+void ColumnSet::Resize(int delta) {
+ LayoutElement::DistributeDelta(delta, &columns_);
+}
+
+// GridLayout -------------------------------------------------------------
+
+GridLayout::GridLayout(View* host)
+ : host_(host),
+ calculated_master_columns_(false),
+ remaining_row_span_(0),
+ current_row_(-1),
+ next_column_(0),
+ current_row_col_set_(NULL),
+ top_inset_(0),
+ bottom_inset_(0),
+ left_inset_(0),
+ right_inset_(0),
+ adding_view_(false) {
+ DCHECK(host);
+}
+
+GridLayout::~GridLayout() {
+ STLDeleteElements(&column_sets_);
+ STLDeleteElements(&view_states_);
+ STLDeleteElements(&rows_);
+}
+
+// static
+GridLayout* GridLayout::CreatePanel(View* host) {
+ GridLayout* layout = new GridLayout(host);
+ layout->SetInsets(kPanelVertMargin, kPanelHorizMargin,
+ kPanelVertMargin, kPanelHorizMargin);
+ return layout;
+}
+
+void GridLayout::SetInsets(int top, int left, int bottom, int right) {
+ top_inset_ = top;
+ bottom_inset_ = bottom;
+ left_inset_ = left;
+ right_inset_ = right;
+}
+
+void GridLayout::SetInsets(const gfx::Insets& insets) {
+ SetInsets(insets.top(), insets.left(), insets.bottom(), insets.right());
+}
+
+ColumnSet* GridLayout::AddColumnSet(int id) {
+ DCHECK(GetColumnSet(id) == NULL);
+ ColumnSet* column_set = new ColumnSet(id);
+ column_sets_.push_back(column_set);
+ return column_set;
+}
+
+void GridLayout::StartRowWithPadding(float vertical_resize, int column_set_id,
+ float padding_resize, int padding) {
+ AddPaddingRow(padding_resize, padding);
+ StartRow(vertical_resize, column_set_id);
+}
+
+void GridLayout::StartRow(float vertical_resize, int column_set_id) {
+ ColumnSet* column_set = GetColumnSet(column_set_id);
+ DCHECK(column_set);
+ AddRow(new Row(false, 0, vertical_resize, column_set));
+}
+
+void GridLayout::AddPaddingRow(float vertical_resize, int pixel_count) {
+ AddRow(new Row(true, pixel_count, vertical_resize, NULL));
+}
+
+void GridLayout::SkipColumns(int col_count) {
+ DCHECK(col_count > 0);
+ next_column_ += col_count;
+ DCHECK(current_row_col_set_ &&
+ next_column_ <= current_row_col_set_->num_columns());
+ SkipPaddingColumns();
+}
+
+void GridLayout::AddView(View* view) {
+ AddView(view, 1, 1);
+}
+
+void GridLayout::AddView(View* view, int col_span, int row_span) {
+ DCHECK(current_row_col_set_ &&
+ next_column_ < current_row_col_set_->num_columns());
+ Column* column = current_row_col_set_->columns_[next_column_];
+ AddView(view, col_span, row_span, column->h_align(), column->v_align());
+}
+
+void GridLayout::AddView(View* view, int col_span, int row_span,
+ Alignment h_align, Alignment v_align) {
+ AddView(view, col_span, row_span, h_align, v_align, 0, 0);
+}
+
+void GridLayout::AddView(View* view, int col_span, int row_span,
+ Alignment h_align, Alignment v_align,
+ int pref_width, int pref_height) {
+ DCHECK(current_row_col_set_ && col_span > 0 && row_span > 0 &&
+ (next_column_ + col_span) <= current_row_col_set_->num_columns());
+ // We don't support baseline alignment of views spanning rows. Please add if
+ // you need it.
+ DCHECK(v_align != BASELINE || row_span == 1);
+ ViewState* state =
+ new ViewState(current_row_col_set_, view, next_column_, current_row_,
+ col_span, row_span, h_align, v_align, pref_width,
+ pref_height);
+ AddViewState(state);
+}
+
+static void CalculateSize(int pref_size, GridLayout::Alignment alignment,
+ int* location, int* size) {
+ if (alignment != GridLayout::FILL) {
+ int available_size = *size;
+ *size = std::min(*size, pref_size);
+ switch (alignment) {
+ case GridLayout::LEADING:
+ // Nothing to do, location already points to start.
+ break;
+ case GridLayout::BASELINE: // If we were asked to align on baseline, but
+ // the view doesn't have a baseline, fall back
+ // to center.
+ case GridLayout::CENTER:
+ *location += (available_size - *size) / 2;
+ break;
+ case GridLayout::TRAILING:
+ *location = *location + available_size - *size;
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+}
+
+void GridLayout::Installed(View* host) {
+ DCHECK(host_ == host);
+}
+
+void GridLayout::Uninstalled(View* host) {
+ DCHECK(host_ == host);
+}
+
+void GridLayout::ViewAdded(View* host, View* view) {
+ DCHECK(host_ == host && adding_view_);
+}
+
+void GridLayout::ViewRemoved(View* host, View* view) {
+ DCHECK(host_ == host);
+}
+
+void GridLayout::Layout(View* host) {
+ DCHECK(host_ == host);
+ // SizeRowsAndColumns sets the size and location of each row/column, but
+ // not of the views.
+ gfx::Size pref;
+ SizeRowsAndColumns(true, host_->width(), host_->height(), &pref);
+
+ // Size each view.
+ for (std::vector<ViewState*>::iterator i = view_states_.begin();
+ i != view_states_.end(); ++i) {
+ ViewState* view_state = *i;
+ ColumnSet* column_set = view_state->column_set;
+ View* view = (*i)->view;
+ DCHECK(view);
+ int x = column_set->columns_[view_state->start_col]->Location() +
+ left_inset_;
+ int width = column_set->GetColumnWidth(view_state->start_col,
+ view_state->col_span);
+ CalculateSize(view_state->pref_width, view_state->h_align,
+ &x, &width);
+ int y = rows_[view_state->start_row]->Location() + top_inset_;
+ int height = LayoutElement::TotalSize(view_state->start_row,
+ view_state->row_span, &rows_);
+ if (view_state->v_align == BASELINE && view_state->baseline != -1) {
+ y += rows_[view_state->start_row]->max_ascent() - view_state->baseline;
+ height = view_state->pref_height;
+ } else {
+ CalculateSize(view_state->pref_height, view_state->v_align, &y, &height);
+ }
+ view->SetBounds(x, y, width, height);
+ }
+}
+
+gfx::Size GridLayout::GetPreferredSize(View* host) {
+ DCHECK(host_ == host);
+ gfx::Size out;
+ SizeRowsAndColumns(false, 0, 0, &out);
+ return out;
+}
+
+int GridLayout::GetPreferredHeightForWidth(View* host, int width) {
+ DCHECK(host_ == host);
+ gfx::Size pref;
+ SizeRowsAndColumns(false, width, 0, &pref);
+ return pref.height();
+}
+
+void GridLayout::SizeRowsAndColumns(bool layout, int width, int height,
+ gfx::Size* pref) {
+ // Make sure the master columns have been calculated.
+ CalculateMasterColumnsIfNecessary();
+ pref->SetSize(0, 0);
+ if (rows_.empty())
+ return;
+
+ // 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.
+ for (std::vector<ColumnSet*>::iterator i = column_sets_.begin();
+ i != column_sets_.end(); ++i) {
+ (*i)->CalculateSize();
+ pref->set_width(std::max(pref->width(), (*i)->LayoutWidth()));
+ }
+ pref->set_width(pref->width() + left_inset_ + right_inset_);
+
+ // Go over the columns again and set them all to the size we settled for.
+ width = width ? width : pref->width();
+ for (std::vector<ColumnSet*>::iterator i = column_sets_.begin();
+ i != column_sets_.end(); ++i) {
+ // We're doing a layout, divy up any extra space.
+ (*i)->Resize(width - (*i)->LayoutWidth() - left_inset_ - right_inset_);
+ // And reset the x coordinates.
+ (*i)->ResetColumnXCoordinates();
+ }
+
+ // Reset the height of each row.
+ LayoutElement::ResetSizes(&rows_);
+
+ // Do the following:
+ // . If the view is aligned along it's baseline, obtain the baseline from the
+ // view and update the rows ascent/descent.
+ // . Reset the remaining_height of each view state.
+ // . If the width the view will be given is different than it's pref, ask
+ // for the height given a particularly width.
+ for (std::vector<ViewState*>::iterator i= view_states_.begin();
+ i != view_states_.end() ; ++i) {
+ ViewState* view_state = *i;
+ view_state->remaining_height = view_state->pref_height;
+
+ if (view_state->v_align == BASELINE)
+ view_state->baseline = view_state->view->GetBaseline();
+
+ if (view_state->h_align == FILL) {
+ // The view is resizable. As the pref height may vary with the width,
+ // ask for the pref again.
+ int actual_width =
+ view_state->column_set->GetColumnWidth(view_state->start_col,
+ view_state->col_span);
+ if (actual_width != view_state->pref_width &&
+ !view_state->pref_height_fixed) {
+ // The width this view will get differs from it's preferred. Some Views
+ // pref height varies with it's width; ask for the preferred again.
+ view_state->pref_height =
+ view_state->view->GetHeightForWidth(actual_width);
+ view_state->remaining_height = view_state->pref_height;
+ }
+ }
+ }
+
+ // Update the height/ascent/descent of each row from the views.
+ std::vector<ViewState*>::iterator view_states_iterator = view_states_.begin();
+ for (; view_states_iterator != view_states_.end() &&
+ (*view_states_iterator)->row_span == 1; ++view_states_iterator) {
+ ViewState* view_state = *view_states_iterator;
+ Row* row = rows_[view_state->start_row];
+ row->AdjustSize(view_state->remaining_height);
+ if (view_state->baseline != -1 &&
+ view_state->baseline <= view_state->pref_height) {
+ row->AdjustSizeForBaseline(view_state->baseline,
+ view_state->pref_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_.end(); ++view_states_iterator) {
+ ViewState* view_state = *view_states_iterator;
+
+ // Update the remaining_width from columns this view_state touches.
+ UpdateRemainingHeightFromRows(view_state);
+
+ // Distribute the remaining height.
+ 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_[rows_.size() - 1]->Location() +
+ rows_[rows_.size() - 1]->Size() + top_inset_ + bottom_inset_);
+
+ if (layout && height != pref->height()) {
+ // We're doing a layout, and the height differs from the preferred height,
+ // divy up the extra space.
+ LayoutElement::DistributeDelta(height - pref->height(), &rows_);
+
+ // Reset y locations.
+ LayoutElement::CalculateLocationsFromSize(&rows_);
+ }
+}
+
+void GridLayout::CalculateMasterColumnsIfNecessary() {
+ if (!calculated_master_columns_) {
+ calculated_master_columns_ = true;
+ for (std::vector<ColumnSet*>::iterator i = column_sets_.begin();
+ i != column_sets_.end(); ++i) {
+ (*i)->CalculateMasterColumns();
+ }
+ }
+}
+
+void GridLayout::AddViewState(ViewState* view_state) {
+ DCHECK(view_state->view && (view_state->view->GetParent() == NULL ||
+ view_state->view->GetParent() == host_));
+ if (!view_state->view->GetParent()) {
+ adding_view_ = true;
+ host_->AddChildView(view_state->view);
+ adding_view_ = false;
+ }
+ remaining_row_span_ = std::max(remaining_row_span_, view_state->row_span);
+ next_column_ += view_state->col_span;
+ current_row_col_set_->AddViewState(view_state);
+ // view_states are ordered by row_span (in ascending order).
+ std::vector<ViewState*>::iterator i = lower_bound(view_states_.begin(),
+ view_states_.end(),
+ view_state,
+ CompareByRowSpan);
+ view_states_.insert(i, view_state);
+ SkipPaddingColumns();
+}
+
+ColumnSet* GridLayout::GetColumnSet(int id) {
+ for (std::vector<ColumnSet*>::iterator i = column_sets_.begin();
+ i != column_sets_.end(); ++i) {
+ if ((*i)->id_ == id) {
+ return *i;
+ }
+ }
+ return NULL;
+}
+
+void GridLayout::AddRow(Row* row) {
+ current_row_++;
+ remaining_row_span_--;
+ DCHECK(remaining_row_span_ <= 0 ||
+ row->column_set() == NULL ||
+ row->column_set() == GetLastValidColumnSet());
+ next_column_ = 0;
+ rows_.push_back(row);
+ current_row_col_set_ = row->column_set();
+ SkipPaddingColumns();
+}
+
+void GridLayout::UpdateRemainingHeightFromRows(ViewState* view_state) {
+ for (int i = 0, start_row = view_state->start_row;
+ i < view_state->row_span; ++i) {
+ view_state->remaining_height -= rows_[i + start_row]->Size();
+ }
+}
+
+void GridLayout::DistributeRemainingHeight(ViewState* view_state) {
+ int height = view_state->remaining_height;
+ if (height <= 0)
+ return;
+
+ // Determine the number of resizable rows the view touches.
+ int resizable_rows = 0;
+ int start_row = view_state->start_row;
+ int max_row = view_state->start_row + view_state->row_span;
+ for (int i = start_row; i < max_row; ++i) {
+ if (rows_[i]->IsResizable()) {
+ resizable_rows++;
+ }
+ }
+
+ if (resizable_rows > 0) {
+ // There are resizable rows, give the remaining height to them.
+ int to_distribute = height / resizable_rows;
+ for (int i = start_row; i < max_row; ++i) {
+ if (rows_[i]->IsResizable()) {
+ height -= to_distribute;
+ if (height < to_distribute) {
+ // Give all slop to the last column.
+ to_distribute += height;
+ }
+ rows_[i]->SetSize(rows_[i]->Size() + to_distribute);
+ }
+ }
+ } else {
+ // None of the rows are resizable, divy the remaining height up equally
+ // among all rows the view touches.
+ int each_row_height = height / view_state->row_span;
+ for (int i = start_row; i < max_row; ++i) {
+ height -= each_row_height;
+ if (height < each_row_height)
+ each_row_height += height;
+ rows_[i]->SetSize(rows_[i]->Size() + each_row_height);
+ }
+ view_state->remaining_height = 0;
+ }
+}
+
+void GridLayout::SkipPaddingColumns() {
+ if (!current_row_col_set_)
+ return;
+ while (next_column_ < current_row_col_set_->num_columns() &&
+ current_row_col_set_->columns_[next_column_]->is_padding_) {
+ next_column_++;
+ }
+}
+
+ColumnSet* GridLayout::GetLastValidColumnSet() {
+ for (int i = current_row_ - 1; i >= 0; --i) {
+ if (rows_[i]->column_set())
+ return rows_[i]->column_set();
+ }
+ return NULL;
+}
+
+} // namespace views
diff --git a/views/layout/grid_layout.h b/views/layout/grid_layout.h
new file mode 100644
index 0000000..f2a0174
--- /dev/null
+++ b/views/layout/grid_layout.h
@@ -0,0 +1,371 @@
+// Copyright (c) 2011 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.
+
+#ifndef VIEWS_LAYOUT_GRID_LAYOUT_H_
+#define VIEWS_LAYOUT_GRID_LAYOUT_H_
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "views/layout/layout_manager.h"
+#include "views/view.h"
+
+namespace gfx {
+class Insets;
+}
+
+// GridLayout is a LayoutManager that positions child Views in a grid. You
+// define the structure of the Grid first, then add the Views.
+// The following creates a trivial grid with two columns separated by
+// a column with padding:
+// ColumnSet* columns = layout->AddColumnSet(0); // Give this column an
+// // identifier of 0.
+// columns->AddColumn(FILL, // Views are horizontally resized to fill column.
+// FILL, // Views starting in this column are vertically
+// // resized.
+// 1, // This column has a resize weight of 1.
+// USE_PREF, // Use the preferred size of the view.
+// 0, // Ignored for USE_PREF.
+// 0); // A minimum width of 0.
+// columns->AddPaddingColumn(0, // The padding column is not resizable.
+// 10); // And has a width of 10 pixels.
+// columns->AddColumn(FILL, FILL, 0, USE_PREF, 0, 0);
+// Now add the views:
+// // First start a row.
+// layout->StartRow(0, // This row isn't vertically resizable.
+// 0); // The column set to use for this row.
+// layout->AddView(v1);
+// Notice you need not skip over padding columns, that's done for you.
+// layout->AddView(v2);
+//
+// When adding a Column you give it the default alignment for all views
+// originating in that column. You can override this for specific views
+// when adding them. For example, the following forces a View to have
+// a horizontal and vertical alignment of leading regardless of that defined
+// for the column:
+// layout->AddView(v1, 1, 1, LEADING, LEADING);
+//
+// If the View using GridLayout is given a size bigger than the preferred,
+// columns and rows with a resize percent > 0 are resized. Each column/row
+// is given resize_percent / total_resize_percent * extra_pixels extra
+// pixels. Only Views with an Alignment of FILL are given extra space, others
+// are aligned in the provided space.
+//
+// GridLayout allows you to define multiple column sets. When you start a
+// new row you specify the id of the column set the row is to use.
+//
+// GridLayout allows you to force columns to have the same width. This is
+// done using the LinkColumnSizes method.
+//
+// AddView takes care of adding the View to the View the GridLayout was
+// created with.
+namespace views {
+
+class Column;
+class ColumnSet;
+class Row;
+class View;
+
+struct ViewState;
+
+class GridLayout : public LayoutManager {
+ public:
+ // An enumeration of the possible alignments supported by GridLayout.
+ enum Alignment {
+ // Leading equates to left along the horizontal axis, and top along the
+ // vertical axis.
+ LEADING,
+
+ // Centers the view along the axis.
+ CENTER,
+
+ // Trailing equals to right along the horizontal axis, and bottom along
+ // the vertical axis.
+ TRAILING,
+
+ // The view is resized to fill the space.
+ FILL,
+
+ // The view is aligned along the baseline. This is only valid for the
+ // vertical axis.
+ BASELINE
+ };
+
+ // An enumeration of the possible ways the size of a column may be obtained.
+ enum SizeType {
+ // The column size is fixed.
+ FIXED,
+
+ // The preferred size of the view is used to determine the column size.
+ USE_PREF
+ };
+
+ explicit GridLayout(View* host);
+ virtual ~GridLayout();
+
+ // Creates a GridLayout with kPanel*Margin insets.
+ static GridLayout* CreatePanel(View* host);
+
+ // Sets the insets. All views are placed relative to these offsets.
+ void SetInsets(int top, int left, int bottom, int right);
+ void SetInsets(const gfx::Insets& insets);
+
+ // Creates a new column set with the specified id and returns it.
+ // The id is later used when starting a new row.
+ // GridLayout takes ownership of the ColumnSet and will delete it when
+ // the GridLayout is deleted.
+ ColumnSet* AddColumnSet(int id);
+
+ // Adds a padding row. Padding rows typically don't have any views, and
+ // but are used to provide vertical white space between views.
+ // Size specifies the height of the row.
+ void AddPaddingRow(float vertical_resize, int size);
+
+ // A convenience for AddPaddingRow followed by StartRow.
+ void StartRowWithPadding(float vertical_resize, int column_set_id,
+ float padding_resize, int padding);
+
+ // Starts a new row with the specified column set.
+ void StartRow(float vertical_resize, int column_set_id);
+
+ // Advances past columns. Use this when the current column should not
+ // contain any views.
+ void SkipColumns(int col_count);
+
+ // Adds a view using the default alignment from the column. The added
+ // view has a column and row span of 1.
+ // As a convenience this adds the view to the host. The view becomes owned
+ // by the host, and NOT this GridLayout.
+ void AddView(View* view);
+
+ // Adds a view using the default alignment from the column.
+ // As a convenience this adds the view to the host. The view becomes owned
+ // by the host, and NOT this GridLayout.
+ void AddView(View* view, int col_span, int row_span);
+
+ // Adds a view with the specified alignment and spans.
+ // As a convenience this adds the view to the host. The view becomes owned
+ // by the host, and NOT this GridLayout.
+ void AddView(View* view, int col_span, int row_span, Alignment h_align,
+ Alignment v_align);
+
+ // Adds a view with the specified alignment and spans. If
+ // pref_width/pref_height is > 0 then the preferred width/height of the view
+ // is fixed to the specified value.
+ // As a convenience this adds the view to the host. The view becomes owned
+ // by the host, and NOT this GridLayout.
+ void AddView(View* view, int col_span, int row_span,
+ Alignment h_align, Alignment v_align,
+ int pref_width, int pref_height);
+
+ // Notification we've been installed on a particular host. Checks that host
+ // is the same as the View supplied in the constructor.
+ virtual void Installed(View* host);
+
+ // Notification we've been uninstalled on a particular host. Checks that host
+ // is the same as the View supplied in the constructor.
+ virtual void Uninstalled(View* host);
+
+ // Notification that a view has been added.
+ virtual void ViewAdded(View* host, View* view);
+
+ // Notification that a view has been removed.
+ virtual void ViewRemoved(View* host, View* view);
+
+ // Layouts out the components.
+ virtual void Layout(View* host);
+
+ // Returns the preferred size for the GridLayout.
+ virtual gfx::Size GetPreferredSize(View* host);
+
+ virtual int GetPreferredHeightForWidth(View* host, int width);
+
+ private:
+ // As both Layout and GetPreferredSize need to do nearly the same thing,
+ // they both call into this method. This sizes the Columns/Rows as
+ // appropriate. If layout is true, width/height give the width/height the
+ // of the host, otherwise they are ignored.
+ void SizeRowsAndColumns(bool layout, int width, int height, gfx::Size* pref);
+
+ // Calculates the master columns of all the column sets. See Column for
+ // a description of what a master column is.
+ void CalculateMasterColumnsIfNecessary();
+
+ // This is called internally from AddView. It adds the ViewState to the
+ // appropriate structures, and updates internal fields such as next_column_.
+ void AddViewState(ViewState* view_state);
+
+ // Returns the column set for the specified id, or NULL if one doesn't exist.
+ ColumnSet* GetColumnSet(int id);
+
+ // Adds the Row to rows_, as well as updating next_column_,
+ // current_row_col_set ...
+ void AddRow(Row* row);
+
+ // As the name says, updates the remaining_height of the ViewState for
+ // all Rows the supplied ViewState touches.
+ void UpdateRemainingHeightFromRows(ViewState* state);
+
+ // If the view state's remaining height is > 0, it is distributed among
+ // the rows the view state touches. This is used during layout to make
+ // sure the Rows can accommodate a view.
+ void DistributeRemainingHeight(ViewState* state);
+
+ // Advances next_column_ past any padding columns.
+ void SkipPaddingColumns();
+
+ // Returns the column set of the last non-padding row.
+ ColumnSet* GetLastValidColumnSet();
+
+ // The view we were created with. We don't own this.
+ View* const host_;
+
+ // Whether or not we've calculated the master/linked columns.
+ bool calculated_master_columns_;
+
+ // Used to verify a view isn't added with a row span that expands into
+ // another column structure.
+ int remaining_row_span_;
+
+ // Current row.
+ int current_row_;
+
+ // Current column.
+ int next_column_;
+
+ // Column set for the current row. This is null for padding rows.
+ ColumnSet* current_row_col_set_;
+
+ // Insets.
+ int top_inset_;
+ int bottom_inset_;
+ int left_inset_;
+ int right_inset_;
+
+ // Set to true when adding a View.
+ bool adding_view_;
+
+ // ViewStates. This is ordered by row_span in ascending order.
+ std::vector<ViewState*> view_states_;
+
+ // ColumnSets.
+ std::vector<ColumnSet*> column_sets_;
+
+ // Rows.
+ std::vector<Row*> rows_;
+
+ DISALLOW_COPY_AND_ASSIGN(GridLayout);
+};
+
+// ColumnSet is used to define a set of columns. GridLayout may have any
+// number of ColumnSets. You don't create a ColumnSet directly, instead
+// use the AddColumnSet method of GridLayout.
+class ColumnSet {
+ public:
+ ~ColumnSet();
+
+ // Adds a column for padding. When adding views, padding columns are
+ // automatically skipped. For example, if you create a column set with
+ // two columns separated by a padding column, the first AddView automatically
+ // skips past the padding column. That is, to add two views, do:
+ // layout->AddView(v1); layout->AddView(v2);, not:
+ // layout->AddView(v1); layout->SkipColumns(1); layout->AddView(v2);
+ void AddPaddingColumn(float resize_percent, int width);
+
+ // Adds a column. The alignment gives the default alignment for views added
+ // with no explicit alignment. fixed_width gives a specific width for the
+ // column, and is only used if size_type == FIXED. min_width gives the
+ // minimum width for the column.
+ //
+ // If none of the columns in a columnset are resizable, the views are only
+ // made as wide as the widest views in each column, even if extra space is
+ // provided. In other words, GridLayout does not automatically resize views
+ // unless the column is marked as resizable.
+ void AddColumn(GridLayout::Alignment h_align,
+ GridLayout::Alignment v_align,
+ float resize_percent,
+ GridLayout::SizeType size_type,
+ int fixed_width,
+ int min_width);
+
+ // Forces the specified columns to have the same size. The size of
+ // linked columns is that of the max of the specified columns. This
+ // must end with -1. For example, the following forces the first and
+ // second column to have the same size:
+ // LinkColumnSizes(0, 1, -1);
+ void LinkColumnSizes(int first, ...);
+
+ // ID of this ColumnSet.
+ int id() const { return id_; }
+
+ int num_columns() { return static_cast<int>(columns_.size()); }
+
+ private:
+ friend class GridLayout;
+
+ explicit ColumnSet(int id);
+
+ void AddColumn(GridLayout::Alignment h_align,
+ GridLayout::Alignment v_align,
+ float resize_percent,
+ GridLayout::SizeType size_type,
+ int fixed_width,
+ int min_width,
+ bool is_padding);
+
+ void AddViewState(ViewState* view_state);
+
+ // Set description of these.
+ void CalculateMasterColumns();
+ void AccumulateMasterColumns();
+
+ // Sets the size of each linked column to be the same.
+ void UnifySameSizedColumnSizes();
+
+ // Updates the remaining width field of the ViewState from that of the
+ // columns the view spans.
+ void UpdateRemainingWidth(ViewState* view_state);
+
+ // Makes sure the columns touched by view state are big enough for the
+ // view.
+ void DistributeRemainingWidth(ViewState* view_state);
+
+ // Returns the total size needed for this ColumnSet.
+ int LayoutWidth();
+
+ // Returns the width of the specified columns.
+ int GetColumnWidth(int start_col, int col_span);
+
+ // Updates the x coordinate of each column from the previous ones.
+ // NOTE: this doesn't include the insets.
+ void ResetColumnXCoordinates();
+
+ // Calculate the preferred width of each view in this column set, as well
+ // as updating the remaining_width.
+ void CalculateSize();
+
+ // Distributes delta amoung the resizable columns.
+ void Resize(int delta);
+
+ // ID for this columnset.
+ const int id_;
+
+ // The columns.
+ std::vector<Column*> columns_;
+
+ // The ViewStates. This is sorted based on column_span in ascending
+ // order.
+ std::vector<ViewState*> view_states_;
+
+ // The master column of those columns that are linked. See Column
+ // for a description of what the master column is.
+ std::vector<Column*> master_columns_;
+
+ DISALLOW_COPY_AND_ASSIGN(ColumnSet);
+};
+
+} // namespace views
+
+#endif // VIEWS_LAYOUT_GRID_LAYOUT_H_
diff --git a/views/layout/grid_layout_unittest.cc b/views/layout/grid_layout_unittest.cc
new file mode 100644
index 0000000..4cd2eae
--- /dev/null
+++ b/views/layout/grid_layout_unittest.cc
@@ -0,0 +1,608 @@
+// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h"
+#include "views/layout/grid_layout.h"
+#include "views/view.h"
+
+using views::ColumnSet;
+using views::GridLayout;
+using views::View;
+
+static void ExpectViewBoundsEquals(int x, int y, int w, int h,
+ const View* view) {
+ EXPECT_EQ(x, view->x());
+ EXPECT_EQ(y, view->y());
+ EXPECT_EQ(w, view->width());
+ EXPECT_EQ(h, view->height());
+}
+
+class SettableSizeView : public View {
+ public:
+ explicit SettableSizeView(const gfx::Size& pref) {
+ pref_ = pref;
+ }
+
+ virtual gfx::Size GetPreferredSize() {
+ return pref_;
+ }
+
+ private:
+ gfx::Size pref_;
+};
+
+// A view with fixed circumference that trades height for width.
+class FlexibleView : public View {
+ public:
+ explicit FlexibleView(int circumference) {
+ circumference_ = circumference;
+ }
+
+ virtual gfx::Size GetPreferredSize() {
+ return gfx::Size(0, circumference_ / 2);
+ }
+
+ virtual int GetHeightForWidth(int width) {
+ return std::max(0, circumference_ / 2 - width);
+ }
+
+ private:
+ int circumference_;
+};
+
+class GridLayoutTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ layout = new GridLayout(&host);
+ }
+
+ virtual void TearDown() {
+ delete layout;
+ }
+
+ virtual void RemoveAll() {
+ for (int i = host.GetChildViewCount() - 1; i >= 0; i--) {
+ host.RemoveChildView(host.GetChildViewAt(i));
+ }
+ }
+
+ void GetPreferredSize() {
+ pref = layout->GetPreferredSize(&host);
+ }
+
+ gfx::Size pref;
+ gfx::Rect bounds;
+ View host;
+ GridLayout* layout;
+};
+
+class GridLayoutAlignmentTest : public testing::Test {
+ public:
+ GridLayoutAlignmentTest() :
+ host(),
+ v1(gfx::Size(10, 20)),
+ layout(new GridLayout(&host)) {}
+
+ virtual void SetUp() {
+ }
+
+ virtual void TearDown() {
+ delete layout;
+ }
+
+ virtual void RemoveAll() {
+ for (int i = host.GetChildViewCount() - 1; i >= 0; i--) {
+ host.RemoveChildView(host.GetChildViewAt(i));
+ }
+ }
+
+ void TestAlignment(GridLayout::Alignment alignment, gfx::Rect* bounds) {
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(alignment, alignment, 1, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(1, 0);
+ layout->AddView(&v1);
+ gfx::Size pref = layout->GetPreferredSize(&host);
+ EXPECT_EQ(gfx::Size(10, 20), pref);
+ host.SetBounds(0, 0, 100, 100);
+ layout->Layout(&host);
+ *bounds = v1.bounds();
+ RemoveAll();
+ }
+
+ View host;
+ SettableSizeView v1;
+ GridLayout* layout;
+};
+
+TEST_F(GridLayoutAlignmentTest, Fill) {
+ gfx::Rect bounds;
+ TestAlignment(GridLayout::FILL, &bounds);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100), bounds);
+}
+
+TEST_F(GridLayoutAlignmentTest, Leading) {
+ gfx::Rect bounds;
+ TestAlignment(GridLayout::LEADING, &bounds);
+ EXPECT_EQ(gfx::Rect(0, 0, 10, 20), bounds);
+}
+
+TEST_F(GridLayoutAlignmentTest, Center) {
+ gfx::Rect bounds;
+ TestAlignment(GridLayout::CENTER, &bounds);
+ EXPECT_EQ(gfx::Rect(45, 40, 10, 20), bounds);
+}
+
+TEST_F(GridLayoutAlignmentTest, Trailing) {
+ gfx::Rect bounds;
+ TestAlignment(GridLayout::TRAILING, &bounds);
+ EXPECT_EQ(gfx::Rect(90, 80, 10, 20), bounds);
+}
+
+TEST_F(GridLayoutTest, TwoColumns) {
+ SettableSizeView v1(gfx::Size(10, 20));
+ SettableSizeView v2(gfx::Size(20, 20));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1);
+ layout->AddView(&v2);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(30, 20), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 10, 20, &v1);
+ ExpectViewBoundsEquals(10, 0, 20, 20, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, ColSpan1) {
+ SettableSizeView v1(gfx::Size(100, 20));
+ SettableSizeView v2(gfx::Size(10, 40));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 1, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1, 2, 1);
+ layout->StartRow(0, 0);
+ layout->AddView(&v2);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(100, 60), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 100, 20, &v1);
+ ExpectViewBoundsEquals(0, 20, 10, 40, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, ColSpan2) {
+ SettableSizeView v1(gfx::Size(100, 20));
+ SettableSizeView v2(gfx::Size(10, 20));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 1, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1, 2, 1);
+ layout->StartRow(0, 0);
+ layout->SkipColumns(1);
+ layout->AddView(&v2);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(100, 40), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 100, 20, &v1);
+ ExpectViewBoundsEquals(90, 20, 10, 20, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, ColSpan3) {
+ SettableSizeView v1(gfx::Size(100, 20));
+ SettableSizeView v2(gfx::Size(10, 20));
+ SettableSizeView v3(gfx::Size(10, 20));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1, 2, 1);
+ layout->StartRow(0, 0);
+ layout->AddView(&v2);
+ layout->AddView(&v3);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(100, 40), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 100, 20, &v1);
+ ExpectViewBoundsEquals(0, 20, 10, 20, &v2);
+ ExpectViewBoundsEquals(50, 20, 10, 20, &v3);
+
+ RemoveAll();
+}
+
+
+TEST_F(GridLayoutTest, ColSpan4) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0,
+ GridLayout::USE_PREF, 0, 0);
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0,
+ GridLayout::USE_PREF, 0, 0);
+
+ SettableSizeView v1(gfx::Size(10, 10));
+ SettableSizeView v2(gfx::Size(10, 10));
+ SettableSizeView v3(gfx::Size(25, 20));
+ layout->StartRow(0, 0);
+ layout->AddView(&v1);
+ layout->AddView(&v2);
+ layout->StartRow(0, 0);
+ layout->AddView(&v3, 2, 1);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(25, 30), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 10, 10, &v1);
+ ExpectViewBoundsEquals(12, 0, 10, 10, &v2);
+ ExpectViewBoundsEquals(0, 10, 25, 20, &v3);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, SameSizeColumns) {
+ SettableSizeView v1(gfx::Size(50, 20));
+ SettableSizeView v2(gfx::Size(10, 10));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ c1->LinkColumnSizes(0, 1, -1);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1);
+ layout->AddView(&v2);
+
+ gfx::Size pref = layout->GetPreferredSize(&host);
+ EXPECT_EQ(gfx::Size(100, 20), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 50, 20, &v1);
+ ExpectViewBoundsEquals(50, 0, 10, 10, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, HorizontalResizeTest1) {
+ SettableSizeView v1(gfx::Size(50, 20));
+ SettableSizeView v2(gfx::Size(10, 10));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::FILL, GridLayout::LEADING,
+ 1, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1);
+ layout->AddView(&v2);
+
+ host.SetBounds(0, 0, 110, 20);
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 100, 20, &v1);
+ ExpectViewBoundsEquals(100, 0, 10, 10, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, HorizontalResizeTest2) {
+ SettableSizeView v1(gfx::Size(50, 20));
+ SettableSizeView v2(gfx::Size(10, 10));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::FILL, GridLayout::LEADING,
+ 1, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::TRAILING, GridLayout::LEADING,
+ 1, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1);
+ layout->AddView(&v2);
+
+ host.SetBounds(0, 0, 120, 20);
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 80, 20, &v1);
+ ExpectViewBoundsEquals(110, 0, 10, 10, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, TestVerticalResize1) {
+ SettableSizeView v1(gfx::Size(50, 20));
+ SettableSizeView v2(gfx::Size(10, 10));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::FILL, GridLayout::FILL,
+ 1, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(1, 0);
+ layout->AddView(&v1);
+ layout->StartRow(0, 0);
+ layout->AddView(&v2);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(50, 30), pref);
+
+ host.SetBounds(0, 0, 50, 100);
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 50, 90, &v1);
+ ExpectViewBoundsEquals(0, 90, 50, 10, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, Insets) {
+ SettableSizeView v1(gfx::Size(10, 20));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ layout->SetInsets(1, 2, 3, 4);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(16, 24), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(2, 1, 10, 20, &v1);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, FixedSize) {
+ layout->SetInsets(2, 2, 2, 2);
+
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ int column_count = 4;
+ int title_width = 100;
+ int row_count = 2;
+ int pref_width = 10;
+ int pref_height = 20;
+
+ for (int i = 0; i < column_count; ++i) {
+ set->AddColumn(views::GridLayout::CENTER,
+ views::GridLayout::CENTER,
+ 0,
+ views::GridLayout::FIXED,
+ title_width,
+ title_width);
+ }
+
+ for (int row = 0; row < row_count; ++row) {
+ layout->StartRow(0, 0);
+ for (int col = 0; col < column_count; ++col) {
+ layout->AddView(new SettableSizeView(gfx::Size(pref_width, pref_height)));
+ }
+ }
+
+ layout->Layout(&host);
+
+ for (int i = 0; i < column_count; ++i) {
+ for (int row = 0; row < row_count; ++row) {
+ View* view = host.GetChildViewAt(row * column_count + i);
+ ExpectViewBoundsEquals(
+ 2 + title_width * i + (title_width - pref_width) / 2,
+ 2 + pref_height * row,
+ pref_width,
+ pref_height, view);
+ }
+ }
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(column_count * title_width + 4,
+ row_count * pref_height + 4), pref);
+}
+
+TEST_F(GridLayoutTest, RowSpanWithPaddingRow) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(views::GridLayout::CENTER,
+ views::GridLayout::CENTER,
+ 0,
+ views::GridLayout::FIXED,
+ 10,
+ 10);
+
+ layout->StartRow(0, 0);
+ layout->AddView(new SettableSizeView(gfx::Size(10, 10)), 1, 2);
+ layout->AddPaddingRow(0, 10);
+}
+
+TEST_F(GridLayoutTest, RowSpan) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(views::GridLayout::LEADING,
+ views::GridLayout::LEADING,
+ 0,
+ views::GridLayout::USE_PREF,
+ 0,
+ 0);
+ set->AddColumn(views::GridLayout::LEADING,
+ views::GridLayout::LEADING,
+ 0,
+ views::GridLayout::USE_PREF,
+ 0,
+ 0);
+
+ layout->StartRow(0, 0);
+ layout->AddView(new SettableSizeView(gfx::Size(20, 10)));
+ layout->AddView(new SettableSizeView(gfx::Size(20, 40)), 1, 2);
+ layout->StartRow(1, 0);
+ views::View* s3 = new SettableSizeView(gfx::Size(20, 10));
+ layout->AddView(s3);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(40, 40), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 10, 20, 10, s3);
+}
+
+TEST_F(GridLayoutTest, RowSpan2) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0,GridLayout::USE_PREF, 0, 0);
+
+ layout->StartRow(0, 0);
+ layout->AddView(new SettableSizeView(gfx::Size(20, 20)));
+ views::View* s3 = new SettableSizeView(gfx::Size(64, 64));
+ layout->AddView(s3, 1, 3);
+
+ layout->AddPaddingRow(0, 10);
+
+ layout->StartRow(0, 0);
+ layout->AddView(new SettableSizeView(gfx::Size(10, 20)));
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(84, 64), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(20, 0, 64, 64, s3);
+}
+
+TEST_F(GridLayoutTest, FixedViewWidth) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0,GridLayout::USE_PREF, 0, 0);
+
+ layout->StartRow(0, 0);
+ View* view = new SettableSizeView(gfx::Size(30, 40));
+ layout->AddView(view, 1, 1, GridLayout::LEADING, GridLayout::LEADING, 10, 0);
+
+ GetPreferredSize();
+ EXPECT_EQ(10, pref.width());
+ EXPECT_EQ(40, pref.height());
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 10, 40, view);
+}
+
+TEST_F(GridLayoutTest, FixedViewHeight) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0,GridLayout::USE_PREF, 0, 0);
+
+ layout->StartRow(0, 0);
+ View* view = new SettableSizeView(gfx::Size(30, 40));
+ layout->AddView(view, 1, 1, GridLayout::LEADING, GridLayout::LEADING, 0, 10);
+
+ GetPreferredSize();
+ EXPECT_EQ(30, pref.width());
+ EXPECT_EQ(10, pref.height());
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 30, 10, view);
+}
+
+// Make sure that for views that span columns the underlying columns are resized
+// based on the resize percent of the column.
+TEST_F(GridLayoutTest, ColumnSpanResizing) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
+ 2, views::GridLayout::USE_PREF, 0, 0);
+ set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
+ 4, views::GridLayout::USE_PREF, 0, 0);
+
+ layout->StartRow(0, 0);
+ // span_view spans two columns and is twice as big the views added below.
+ View* span_view = new SettableSizeView(gfx::Size(12, 40));
+ layout->AddView(span_view, 2, 1, GridLayout::LEADING, GridLayout::LEADING);
+
+ layout->StartRow(0, 0);
+ View* view1 = new SettableSizeView(gfx::Size(2, 40));
+ View* view2 = new SettableSizeView(gfx::Size(4, 40));
+ layout->AddView(view1);
+ layout->AddView(view2);
+
+ host.SetBounds(0, 0, 12, 80);
+ layout->Layout(&host);
+
+ ExpectViewBoundsEquals(0, 0, 12, 40, span_view);
+
+ // view1 should be 4 pixels wide
+ // column_pref + (remaining_width * column_resize / total_column_resize) =
+ // 2 + (6 * 2 / 6).
+ ExpectViewBoundsEquals(0, 40, 4, 40, view1);
+
+ // And view2 should be 8 pixels wide:
+ // 4 + (6 * 4 / 6).
+ ExpectViewBoundsEquals(4, 40, 8, 40, view2);
+}
+
+// Check that GetPreferredSize() takes resizing of columns into account when
+// there is additional space in the case we have column sets of different
+// preferred sizes.
+TEST_F(GridLayoutTest, ColumnResizingOnGetPreferredSize) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+ set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
+ 1, views::GridLayout::USE_PREF, 0, 0);
+
+ set = layout->AddColumnSet(1);
+ set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
+ 1, views::GridLayout::USE_PREF, 0, 0);
+
+ set = layout->AddColumnSet(2);
+ set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
+ 1, views::GridLayout::USE_PREF, 0, 0);
+
+ // Make a row containing a flexible view that trades width for height.
+ layout->StartRow(0, 0);
+ View* view1 = new FlexibleView(100);
+ layout->AddView(view1, 1, 1, GridLayout::FILL, GridLayout::LEADING);
+
+ // The second row contains a view of fixed size that will enforce a column
+ // width of 20 pixels.
+ layout->StartRow(0, 1);
+ View* view2 = new SettableSizeView(gfx::Size(20, 20));
+ layout->AddView(view2, 1, 1, GridLayout::FILL, GridLayout::LEADING);
+
+ // Add another flexible view in row three in order to ensure column set
+ // ordering doesn't influence sizing behaviour.
+ layout->StartRow(0, 2);
+ View* view3 = new FlexibleView(40);
+ layout->AddView(view3, 1, 1, GridLayout::FILL, GridLayout::LEADING);
+
+ // We expect a height of 50: 30 from the variable width view in the first row
+ // plus 20 from the statically sized view in the second row. The flexible
+ // view in the third row should contribute no height.
+ EXPECT_EQ(gfx::Size(20, 50), layout->GetPreferredSize(&host));
+}