diff options
author | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-27 02:17:21 +0000 |
---|---|---|
committer | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-27 02:17:21 +0000 |
commit | 9ef7d4bcd2bbd016f92d67c4aea8a45f29c8d3ee (patch) | |
tree | ac0ac694511db7a15f3e6440d1bd69af2efd003e /views/layout | |
parent | 74c3fb5ff6eec3ce2b9c18aeb0aab42c0db25034 (diff) | |
download | chromium_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.cc | 1070 | ||||
-rw-r--r-- | views/layout/grid_layout.h | 371 | ||||
-rw-r--r-- | views/layout/grid_layout_unittest.cc | 608 |
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)); +} |