diff options
author | groby@chromium.org <groby@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-14 04:49:09 +0000 |
---|---|---|
committer | groby@chromium.org <groby@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-14 04:49:09 +0000 |
commit | 36986daae4fb3e5875adc7cddca2fb704b9057dd (patch) | |
tree | 0f271a77e2371c5bea14b8efd1f93a0c844ccf5b /chrome/browser/ui | |
parent | 1a057065240b4e43c4ff54eb3ce7ac00a7deef9a (diff) | |
download | chromium_src-36986daae4fb3e5875adc7cddca2fb704b9057dd.zip chromium_src-36986daae4fb3e5875adc7cddca2fb704b9057dd.tar.gz chromium_src-36986daae4fb3e5875adc7cddca2fb704b9057dd.tar.bz2 |
[Autofill] Add basic version of GridLayout
R=sail@chromium.org
BUG=157274
Review URL: https://chromiumcodereview.appspot.com/14646021
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@199901 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/ui')
-rw-r--r-- | chrome/browser/ui/cocoa/autofill/layout_view.h | 34 | ||||
-rw-r--r-- | chrome/browser/ui/cocoa/autofill/layout_view.mm | 32 | ||||
-rw-r--r-- | chrome/browser/ui/cocoa/autofill/layout_view_unittest.mm | 65 | ||||
-rw-r--r-- | chrome/browser/ui/cocoa/autofill/simple_grid_layout.h | 140 | ||||
-rw-r--r-- | chrome/browser/ui/cocoa/autofill/simple_grid_layout.mm | 381 | ||||
-rw-r--r-- | chrome/browser/ui/cocoa/autofill/simple_grid_layout_unittest.mm | 397 |
6 files changed, 1049 insertions, 0 deletions
diff --git a/chrome/browser/ui/cocoa/autofill/layout_view.h b/chrome/browser/ui/cocoa/autofill/layout_view.h new file mode 100644 index 0000000..4a9dc58 --- /dev/null +++ b/chrome/browser/ui/cocoa/autofill/layout_view.h @@ -0,0 +1,34 @@ +// Copyright (c) 2013 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 CHROME_BROWSER_UI_COCOA_AUTOFILL_LAYOUT_VIEW_H_ +#define CHROME_BROWSER_UI_COCOA_AUTOFILL_LAYOUT_VIEW_H_ + +#import <Cocoa/Cocoa.h> + +#include "base/memory/scoped_ptr.h" + +class SimpleGridLayout; + +// A view that carries its own layout manager. Re-layouts on setFrame:. +@interface LayoutView : NSView { + @private + scoped_ptr<SimpleGridLayout> layout_; +} + +// Sets a layout manager and takes ownership of it. +- (void)setLayoutManager:(scoped_ptr<SimpleGridLayout>)layout; + +// Return a pointer to layout manager, still owned by the view. +- (SimpleGridLayout*)layoutManager; + +// Re-layout subviews according to layout manager. +- (void)performLayout; + +// Query the layout manager for the best height for specified |width|. +- (CGFloat)preferredHeightForWidth:(CGFloat)width; + +@end + +#endif // CHROME_BROWSER_UI_COCOA_AUTOFILL_LAYOUT_VIEW_H_ diff --git a/chrome/browser/ui/cocoa/autofill/layout_view.mm b/chrome/browser/ui/cocoa/autofill/layout_view.mm new file mode 100644 index 0000000..04881e2 --- /dev/null +++ b/chrome/browser/ui/cocoa/autofill/layout_view.mm @@ -0,0 +1,32 @@ +// Copyright (c) 2013 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. + +#import "chrome/browser/ui/cocoa/autofill/layout_view.h" + +#include "chrome/browser/ui/cocoa/autofill/simple_grid_layout.h" + +@implementation LayoutView + +- (void)setLayoutManager:(scoped_ptr<SimpleGridLayout>)layout { + layout_ = layout.Pass(); +} + +- (SimpleGridLayout*)layoutManager { + return layout_.get(); +} + +- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize { + [super resizeSubviewsWithOldSize:oldBoundsSize]; + [self performLayout]; +} + +- (void)performLayout { + layout_->Layout(self); +} + +- (CGFloat)preferredHeightForWidth:(CGFloat)width { + return layout_->GetPreferredHeightForWidth(width); +} + +@end diff --git a/chrome/browser/ui/cocoa/autofill/layout_view_unittest.mm b/chrome/browser/ui/cocoa/autofill/layout_view_unittest.mm new file mode 100644 index 0000000..abfcd227 --- /dev/null +++ b/chrome/browser/ui/cocoa/autofill/layout_view_unittest.mm @@ -0,0 +1,65 @@ +// Copyright (c) 2013 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. + +#import "chrome/browser/ui/cocoa/autofill/layout_view.h" + +#include "base/memory/scoped_nsobject.h" +#include "chrome/browser/ui/cocoa/autofill/simple_grid_layout.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" +#import "ui/base/test/ui_cocoa_test_helper.h" + +namespace { + +class LayoutViewTest : public ui::CocoaTest { + public: + LayoutViewTest() { + CocoaTest::SetUp(); + view_.reset([[LayoutView alloc] initWithFrame:NSZeroRect]); + [view_ setLayoutManager: + scoped_ptr<SimpleGridLayout>(new SimpleGridLayout(view_))]; + layout_ = [view_ layoutManager]; + [[test_window() contentView] addSubview:view_]; + } + + protected: + scoped_nsobject<LayoutView> view_; + SimpleGridLayout* layout_; // weak, owned by view_. +}; + +} // namespace + +TEST_VIEW(LayoutViewTest, view_) + +TEST_F(LayoutViewTest, setFrameInvokesLayout) { + EXPECT_FLOAT_EQ(0, NSWidth([view_ frame])); + EXPECT_FLOAT_EQ(0, NSHeight([view_ frame])); + + ColumnSet* cs = layout_->AddColumnSet(0); + cs->AddColumn(0.4); + cs->AddColumn(0.6); + layout_->StartRow(0, 0); + scoped_nsobject<NSView> childView1( + [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 45)]); + scoped_nsobject<NSView> childView2( + [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 20, 55)]); + layout_->AddView(childView1); + layout_->AddView(childView2); + + ASSERT_EQ(2U, [[view_ subviews] count]); + EXPECT_FLOAT_EQ(0, NSWidth([view_ frame])); + EXPECT_FLOAT_EQ(0, NSHeight([view_ frame])); + + [view_ setFrame:NSMakeRect(0, 0, 40, 60)]; + EXPECT_FLOAT_EQ(40, NSWidth([view_ frame])); + EXPECT_FLOAT_EQ(60, NSHeight([view_ frame])); + + NSView* subview = [[view_ subviews] objectAtIndex:0]; + EXPECT_FLOAT_EQ(16, NSWidth([subview frame])); + EXPECT_FLOAT_EQ(55, NSHeight([subview frame])); + + subview = [[view_ subviews] objectAtIndex:1]; + EXPECT_FLOAT_EQ(24, NSWidth([subview frame])); + EXPECT_FLOAT_EQ(55, NSHeight([subview frame])); +}
\ No newline at end of file diff --git a/chrome/browser/ui/cocoa/autofill/simple_grid_layout.h b/chrome/browser/ui/cocoa/autofill/simple_grid_layout.h new file mode 100644 index 0000000..e3614a4 --- /dev/null +++ b/chrome/browser/ui/cocoa/autofill/simple_grid_layout.h @@ -0,0 +1,140 @@ +// Copyright (c) 2013 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 CHROME_BROWSER_UI_COCOA_AUTOFILL_SIMPLE_GRID_LAYOUT_H_ +#define CHROME_BROWSER_UI_COCOA_AUTOFILL_SIMPLE_GRID_LAYOUT_H_ + +#import <Cocoa/Cocoa.h> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" + +class Column; +class ColumnSet; +class Row; +class ViewState; + +// SimpleGridLayout is a layout manager that positions child views in a grid. +// Each row has exactly one ColumnSet, ColumnSets can be shared between rows. +// See ui/views/layout/grid_layout.h for more details - this is a very +// simplified version of the views class. +// This is based on GridLayout in ui/views/layout/grid_layout.h. +// TODO(groby): Unify both classes, create cross-platform abstraction. +// http://crbug.com/240461 +class SimpleGridLayout { + public: + SimpleGridLayout(NSView* host); + ~SimpleGridLayout(); + + // Creates a new column set with the specified id and returns it. + // The id is later used when starting a new row. + // Layout takes ownership of the ColumnSet and will delete it when + // it is deleted. + ColumnSet* AddColumnSet(int id); + + // Returns the column set for the specified id, or NULL if one doesn't exist. + ColumnSet* GetColumnSet(int id); + + // Adds a padding row. Padding rows typically don't have any views, but are + // used to provide vertical white space between views. + // |size| specifies the height of the row. + void AddPaddingRow(int size); + + // Starts a new row with the specified column set. + void StartRow(float vertical_resize, int column_set_id); + + // This is a convenience function that starts a new row, + // and returns a new ColumnSet associated with it. All rows created by this + // will have a height of 0 and resize_percent set to 1.0. + ColumnSet* AddRow(); + + // Advances past columns. Use this when the current column should not + // contain any views. + void SkipColumns(int col_count); + + // TODO(groby): This currently *must* be called after a StartRow for the row + // the view is in. At some point, I'd like an AddView that just populates + // the next available slot, if possible. + void AddView(NSView* view); + + // Layout all contained views according to constraints. + void Layout(NSView* superView); + + void SizeRowsAndColumns(float width); + + // Advances next_column_ past any padding columns. + void SkipPaddingColumns(); + + // Returns the column set of the last non-padding row. + ColumnSet* GetLastValidColumnSet(); + + // Get the height of a given row. + float GetRowHeight(int row); + + // Y-position for a given row. + float GetRowLocation(int row_index) const; + + // Get the preferred height for the given width. + float GetPreferredHeightForWidth(float with); + + int num_rows() const { return static_cast<int>(rows_.size()); } + + // These functions are mostly for testing & deviate from Views Layout class. + int next_column() { return next_column_; } + void AdvanceColumn() { next_column_++; } + + private: + // Adds a new row, updating associated counters and positions. + void AddRow(Row* row); + + // Next column in the current ColumnSet. + int next_column_; + + int current_auto_id_; // Starting value for autogenerated columnset ids. + ScopedVector<ViewState> view_states_; + ScopedVector<ColumnSet> column_sets_; + ScopedVector<Row> rows_; + + NSView* host_; +}; + +// ColumnSet is used to define a set of columns. +// You don't create a ColumnSet directly, instead use the AddRow method +// of SimpleGridLayout. +class ColumnSet { + public: + explicit ColumnSet(int id); + ~ColumnSet(); + + void AddPaddingColumn(int fixed_width); + void AddColumn(float resize_percent); + + void CalculateSize(float width); + void ResetColumnXCoordinates(); + + // ID of this ColumnSet. + int id() const { return id_; } + + int num_columns() const { return static_cast<int>(columns_.size()); } + + // Returns the width of the specified columns. + float GetColumnWidth(int column); + + Column* GetColumn(int column_index) { + DCHECK(column_index >=0 && column_index < num_columns()); + return columns_[column_index]; + } + + // These functions are mostly for testing & deviate from Views Layout class. + float ColumnLocation(int column_index); + + private: + float CalculateRemainingWidth(float width); + void DistributeRemainingWidth(float width); + + ScopedVector<Column> columns_; + int id_; +}; + +#endif // CHROME_BROWSER_UI_COCOA_AUTOFILL_SIMPLE_GRID_LAYOUT_H_ diff --git a/chrome/browser/ui/cocoa/autofill/simple_grid_layout.mm b/chrome/browser/ui/cocoa/autofill/simple_grid_layout.mm new file mode 100644 index 0000000..6de0bbf --- /dev/null +++ b/chrome/browser/ui/cocoa/autofill/simple_grid_layout.mm @@ -0,0 +1,381 @@ +// Copyright (c) 2013 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 "chrome/browser/ui/cocoa/autofill/simple_grid_layout.h" + +#include <algorithm> + +#include "base/logging.h" +#include "base/stl_util.h" + +namespace { +const int kAutoColumnIdStart = 1000000; // Starting ID for autogeneration. +} + +// Encapsulates state for a single NSView in the layout +class ViewState { + public: + ViewState(NSView* view, ColumnSet* column_set, int row, int column); + + // Gets the current width of the column associated with this view. + float GetColumnWidth(); + + // Get the preferred height for specified width. + float GetHeightForWidth(float with); + + Column* GetColumn() const { return column_set_->GetColumn(column_); } + + int row_index() { return row_; } + NSView* view() { return view_; } + float preferred_height() { return pref_height_; } + void set_preferred_height(float height) { pref_height_ = height; } + + private: + NSView* view_; + ColumnSet* column_set_; + int row_; + int column_; + float pref_height_; +}; + +class LayoutElement { + public: + LayoutElement(float resize_percent, int fixed_width); + virtual ~LayoutElement() {} + + template <class T> + static void ResetSizes(ScopedVector<T>* elements) { + // Reset the layout width of each column. + for (typename std::vector<T*>::iterator i = elements->begin(); + i != elements->end(); ++i) { + (*i)->ResetSize(); + } + } + + template <class T> + static void CalculateLocationsFromSize(ScopedVector<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(); + } + } + + float Size() { return size_; } + + void ResetSize() { + size_ = fixed_width_; + } + + void SetSize(float size) { + size_ = size; + } + + float Location() const { + return location_; + } + + // Adjusts the size of this LayoutElement to be the max of the current size + // and the specified size. + virtual void AdjustSize(float size) { + size_ = std::max(size_, size); + } + + void SetLocation(float location) { + location_ = location; + } + + bool IsResizable() { + return resize_percent_ > 0.0f; + } + + float ResizePercent() { + return resize_percent_; + } + + private: + float resize_percent_; + int fixed_width_; + float size_; + float location_; +}; + +LayoutElement::LayoutElement(float resize_percent, int fixed_width) + : resize_percent_(resize_percent), + fixed_width_(fixed_width), + size_(0), + location_(0) { +} + +class Column : public LayoutElement { + public: + Column(float resize_percent, int fixed_width, bool is_padding); + + bool is_padding() { return is_padding_; } + + private: + const bool is_padding_; +}; + +Column::Column(float resize_percent, int fixed_width, bool is_padding) + : LayoutElement(resize_percent, fixed_width), + is_padding_(is_padding) { +} + +class Row : public LayoutElement { + public: + Row(float resize_percent, int fixed_height, ColumnSet* column_set); + + ColumnSet* column_set() { return column_set_; } + + private: + ColumnSet* column_set_; +}; + +Row::Row(float resize_percent, int fixed_height, ColumnSet* column_set) + : LayoutElement(resize_percent, fixed_height), + column_set_(column_set) { +} + +ViewState::ViewState(NSView* view, ColumnSet* column_set, int row, int column) + : view_(view), + column_set_(column_set), + row_(row), + column_(column) {} + +float ViewState::GetColumnWidth() { + return column_set_->GetColumnWidth(column_); +} + +float ViewState::GetHeightForWidth(float width) { + // NSView doesn't have any way to make height fit size, get frame height. + return NSHeight([view_ frame]); +} + +ColumnSet::ColumnSet(int id) : id_(id) { +} + +ColumnSet::~ColumnSet() { +} + +void ColumnSet::AddPaddingColumn(int fixed_width) { + columns_.push_back(new Column(0.0f, fixed_width, true)); +} + +void ColumnSet::AddColumn(float resize_percent) { + columns_.push_back(new Column(resize_percent, 0, false)); +} + +void ColumnSet::CalculateSize(float width) { + // Reset column widths + LayoutElement::ResetSizes(&columns_); + width = CalculateRemainingWidth(width); + DistributeRemainingWidth(width); +} + +void ColumnSet::ResetColumnXCoordinates() { + LayoutElement::CalculateLocationsFromSize(&columns_); +} + +float ColumnSet::CalculateRemainingWidth(float width) { + for (size_t i = 0; i < columns_.size(); ++i) + width -= columns_[i]->Size(); + + return width; +} + +void ColumnSet::DistributeRemainingWidth(float width) { + float total_resize = 0.0f; + int resizable_columns = 0.0; + + for (size_t i = 0; i < columns_.size(); ++i) { + if (columns_[i]->IsResizable()) { + total_resize += columns_[i]->ResizePercent(); + resizable_columns++; + } + } + + float remaining_width = width; + for (size_t i = 0; i < columns_.size(); ++i) { + if (columns_[i]->IsResizable()) { + float delta = (resizable_columns == 0) ? remaining_width : + (width * columns_[i]->ResizePercent() / total_resize); + remaining_width -= delta; + columns_[i]->SetSize(columns_[i]->Size() + delta); + resizable_columns--; + } + } +} + +float ColumnSet::GetColumnWidth(int column_index) { + if (column_index < 0 || column_index >= num_columns()) + return 0.0; + return columns_[column_index]->Size(); +} + +float ColumnSet::ColumnLocation(int column_index) { + if (column_index < 0 || column_index >= num_columns()) + return 0.0; + return columns_[column_index]->Location(); +} + +SimpleGridLayout::SimpleGridLayout(NSView* host) + : next_column_(0), + current_auto_id_(kAutoColumnIdStart), + host_(host) { + [host_ frame]; +} + +SimpleGridLayout::~SimpleGridLayout() { +} + +ColumnSet* SimpleGridLayout::AddColumnSet(int id) { + DCHECK(GetColumnSet(id) == NULL); + ColumnSet* column_set = new ColumnSet(id); + column_sets_.push_back(column_set); + return column_set; +} + +ColumnSet* SimpleGridLayout::GetColumnSet(int id) { + for (ScopedVector<ColumnSet>::const_iterator i = column_sets_.begin(); + i != column_sets_.end(); ++i) { + if ((*i)->id() == id) { + return *i; + } + } + return NULL; +} + +void SimpleGridLayout::AddPaddingRow(int fixed_height) { + AddRow(new Row(0.0f, fixed_height, NULL)); +} + +void SimpleGridLayout::StartRow(float vertical_resize, int column_set_id) { + ColumnSet* column_set = GetColumnSet(column_set_id); + DCHECK(column_set); + AddRow(new Row(vertical_resize, 0, column_set)); +} + +ColumnSet* SimpleGridLayout::AddRow() { + AddRow(new Row(0, 0, AddColumnSet(current_auto_id_++))); + return column_sets_.back(); +} + +void SimpleGridLayout::SkipColumns(int col_count) { + DCHECK(col_count > 0); + next_column_ += col_count; + ColumnSet* current_row_col_set_ = GetLastValidColumnSet(); + DCHECK(current_row_col_set_ && + next_column_ <= current_row_col_set_->num_columns()); + SkipPaddingColumns(); +} + +void SimpleGridLayout::AddView(NSView* view) { + [host_ addSubview:view]; + DCHECK(next_column_ < GetLastValidColumnSet()->num_columns()); + view_states_.push_back( + new ViewState(view, + GetLastValidColumnSet(), + rows_.size() - 1, + next_column_++)); + SkipPaddingColumns(); +} + +// Sizes elements to fit into the superViews bounds, according to constraints. +void SimpleGridLayout::Layout(NSView* superView) { + SizeRowsAndColumns(NSWidth([superView bounds])); + for (std::vector<ViewState*>::iterator i = view_states_.begin(); + i != view_states_.end(); ++i) { + ViewState* view_state = *i; + NSView* view = view_state->view(); + NSRect frame = NSMakeRect(view_state->GetColumn()->Location(), + rows_[view_state->row_index()]->Location(), + view_state->GetColumn()->Size(), + rows_[view_state->row_index()]->Size()); + [view setFrame:frame]; + } +} + +void SimpleGridLayout::SizeRowsAndColumns(float width) { + // Size all columns first. + for (ScopedVector<ColumnSet>::iterator i = column_sets_.begin(); + i != column_sets_.end(); ++i) { + (*i)->CalculateSize(width); + (*i)->ResetColumnXCoordinates(); + } + + // Reset the height of each row. + LayoutElement::ResetSizes(&rows_); + + // For each ViewState, obtain the preferred height + for (std::vector<ViewState*>::iterator i= view_states_.begin(); + i != view_states_.end() ; ++i) { + ViewState* view_state = *i; + + // The view is resizable. As the pref height may vary with the width, + // ask for the pref again. + int actual_width = view_state->GetColumnWidth(); + + // The width this view will get differs from its preferred. Some Views + // pref height varies with its width; ask for the preferred again. + view_state->set_preferred_height( + view_state->GetHeightForWidth(actual_width)); + } + + + // Make sure each row can accommodate all contained ViewStates. + std::vector<ViewState*>::iterator view_states_iterator = view_states_.begin(); + for (; view_states_iterator != view_states_.end(); ++view_states_iterator) { + ViewState* view_state = *view_states_iterator; + Row* row = rows_[view_state->row_index()]; + row->AdjustSize(view_state->preferred_height()); + } + + // Update the location of each of the rows. + LayoutElement::CalculateLocationsFromSize(&rows_); +} + +void SimpleGridLayout::SkipPaddingColumns() { + ColumnSet* current_row_col_set_ = GetLastValidColumnSet(); + while (next_column_ < current_row_col_set_->num_columns() && + current_row_col_set_->GetColumn(next_column_)->is_padding()) { + next_column_++; + } +} + +ColumnSet* SimpleGridLayout::GetLastValidColumnSet() { + for (int i = num_rows() - 1; i >= 0; --i) { + if (rows_[i]->column_set()) + return rows_[i]->column_set(); + } + return NULL; +} + +float SimpleGridLayout::GetRowHeight(int row_index) { + if (row_index < 0 || row_index >= num_rows()) + return 0.0; + return rows_[row_index]->Size(); +} + +float SimpleGridLayout::GetRowLocation(int row_index) const { + if (row_index < 0 || row_index >= num_rows()) + return 0.0; + return rows_[row_index]->Location(); +} + +float SimpleGridLayout::GetPreferredHeightForWidth(float width) { + if (rows_.empty()) + return 0.0f; + + SizeRowsAndColumns(width); + return rows_.back()->Location() + rows_.back()->Size(); +} + +void SimpleGridLayout::AddRow(Row* row) { + next_column_ = 0; + rows_.push_back(row); +} + diff --git a/chrome/browser/ui/cocoa/autofill/simple_grid_layout_unittest.mm b/chrome/browser/ui/cocoa/autofill/simple_grid_layout_unittest.mm new file mode 100644 index 0000000..d529211 --- /dev/null +++ b/chrome/browser/ui/cocoa/autofill/simple_grid_layout_unittest.mm @@ -0,0 +1,397 @@ +// Copyright (c) 2013 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 "chrome/browser/ui/cocoa/autofill/simple_grid_layout.h" + +#include "base/logging.h" +#include "base/memory/scoped_nsobject.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Layout objects as multiple columns in a single row. +class ColumnLayout : public SimpleGridLayout { + public: + ColumnLayout(NSView* host) : SimpleGridLayout(host) { AddRow(); } + ~ColumnLayout() {} +}; + +class LayoutTest : public testing::Test { + public: + scoped_nsobject<NSView> CreateViewWithWidth(float width) { + NSRect frame = NSMakeRect(0, 0, width, 0); + scoped_nsobject<NSView> view([[NSView alloc] initWithFrame:frame]); + return view; + } + + scoped_nsobject<NSView> CreateViewWithHeight(float height) { + NSRect frame = NSMakeRect(0, 0, 0, height); + scoped_nsobject<NSView> view([[NSView alloc] initWithFrame:frame]); + return view; + } +}; + +TEST_F(LayoutTest, GridLayoutAddGetColumnSet) { + SimpleGridLayout layout(NULL); + + // Non-existing id's are not found. + EXPECT_EQ(0L, layout.GetColumnSet(1)); + + // Created ColumnSets can be retrieved. + ColumnSet* set1 = layout.AddColumnSet(10); + ColumnSet* set2 = layout.AddColumnSet(11); + ColumnSet* set3 = layout.AddColumnSet(12); + EXPECT_EQ(set2, layout.GetColumnSet(11)); + EXPECT_EQ(set1, layout.GetColumnSet(10)); + EXPECT_EQ(set3, layout.GetColumnSet(12)); +} + +TEST_F(LayoutTest, AddRowAutogeneratesColumnSetId) { + SimpleGridLayout layout(NULL); + + ColumnSet* set1 = layout.AddRow(); + EXPECT_NE(0, set1->id()); + + ColumnSet* set2 = layout.AddRow(); + EXPECT_NE(set1->id(), set2->id()); +} + +TEST_F(LayoutTest, AddRowResetsNextColumn) { + SimpleGridLayout layout(NULL); + layout.AddRow(); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + layout.AddView(nil); + EXPECT_EQ(1, layout.next_column()); + + layout.AddRow(); + EXPECT_EQ(0, layout.next_column()); +} + +TEST_F(LayoutTest, GetLastValidColumnSet) { + SimpleGridLayout layout(NULL); + + EXPECT_EQ(NULL, layout.GetLastValidColumnSet()); + + ColumnSet* cs1 = layout.AddColumnSet(1); + ColumnSet* cs2 = layout.AddColumnSet(2); + layout.StartRow(0.0, 2); + EXPECT_EQ(cs2, layout.GetLastValidColumnSet()); + + layout.AddPaddingRow(10); + EXPECT_EQ(cs2, layout.GetLastValidColumnSet()); + + layout.StartRow(0.0, 1); + EXPECT_EQ(cs1, layout.GetLastValidColumnSet()); +} + + +TEST_F(LayoutTest, AddRowCreatesNewColumnSet) { + SimpleGridLayout layout(NULL); + + EXPECT_EQ(NULL, layout.GetLastValidColumnSet()); + + ColumnSet* old_column_set = layout.AddRow(); + EXPECT_TRUE(old_column_set != NULL); + EXPECT_EQ(old_column_set, layout.GetLastValidColumnSet()); + + ColumnSet* new_column_set = layout.AddRow(); + EXPECT_TRUE(new_column_set != NULL); + EXPECT_EQ(new_column_set, layout.GetLastValidColumnSet()); +} + +TEST_F(LayoutTest, AddPaddingRow) { + SimpleGridLayout layout(NULL); + + ColumnSet* cs; + cs = layout.AddRow(); + cs->AddColumn(1.0f); + scoped_nsobject<NSView> view1 = CreateViewWithHeight(30.0f); + layout.AddView(view1); + + layout.AddPaddingRow(20); + + cs = layout.AddRow(); + cs->AddColumn(1.0f); + EXPECT_EQ(3, layout.num_rows()); + scoped_nsobject<NSView> view2 = CreateViewWithHeight(10.0f); + layout.AddView(view2); + + layout.SizeRowsAndColumns(0.0f); + EXPECT_FLOAT_EQ(30.0f, layout.GetRowHeight(0)); + EXPECT_FLOAT_EQ(20.0f, layout.GetRowHeight(1)); + EXPECT_FLOAT_EQ(10.0f, layout.GetRowHeight(2)); +} + +TEST_F(LayoutTest, RowsAccomodateHeightOfAllElements) { + SimpleGridLayout layout(NULL); + + layout.AddRow(); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + + scoped_nsobject<NSView> view1 = CreateViewWithHeight(37.0f); + layout.AddView(view1); + layout.SizeRowsAndColumns(0.0f); + EXPECT_FLOAT_EQ(37.0f, layout.GetRowHeight(0)); + + scoped_nsobject<NSView> view2 = CreateViewWithHeight(26.0f); + layout.AddView(view2); + layout.SizeRowsAndColumns(0.0f); + EXPECT_FLOAT_EQ(37.0f, layout.GetRowHeight(0)); + + scoped_nsobject<NSView> view3 = CreateViewWithHeight(42.0f); + layout.AddView(view3); + layout.SizeRowsAndColumns(0.0f); + EXPECT_FLOAT_EQ(42.0f, layout.GetRowHeight(0)); +} + +TEST_F(LayoutTest, SizeRowsAdjustsRowLocations) { + SimpleGridLayout layout(NULL); + + layout.AddRow(); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + scoped_nsobject<NSView> view1 = CreateViewWithHeight(30.0f); + layout.AddView(view1); + + layout.AddRow(); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + scoped_nsobject<NSView> view2 = CreateViewWithHeight(40.0f); + layout.AddView(view2); + + layout.SizeRowsAndColumns(0.0f); + EXPECT_FLOAT_EQ(0.0f, layout.GetRowLocation(0)); + EXPECT_FLOAT_EQ(30.0f, layout.GetRowLocation(1)); +} + +TEST_F(LayoutTest, SimpleGridLayoutAdjustsViews) { + SimpleGridLayout layout(NULL); + + layout.AddRow(); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + layout.GetLastValidColumnSet()->AddPaddingColumn(30); + layout.GetLastValidColumnSet()->AddColumn(0.4f); + + scoped_nsobject<NSView> view1 = CreateViewWithHeight(22.0f); + scoped_nsobject<NSView> view2 = CreateViewWithHeight(20.0f); + layout.AddView(view1); + layout.AddView(view2); + + layout.AddRow(); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + scoped_nsobject<NSView> view3 = CreateViewWithHeight(18.0f); + layout.AddView(view3); + + scoped_nsobject<NSView> host_view = CreateViewWithWidth(150.0f); + layout.Layout(host_view); + + EXPECT_FLOAT_EQ(72.0f, NSWidth([view1 frame])); + EXPECT_FLOAT_EQ(22.0f, NSHeight([view1 frame])); + EXPECT_FLOAT_EQ(0.0f, NSMinX([view1 frame])); + EXPECT_FLOAT_EQ(0.0f, NSMinY([view1 frame])); + + EXPECT_FLOAT_EQ(48.0f, NSWidth([view2 frame])); + EXPECT_FLOAT_EQ(22.0f, NSHeight([view2 frame])); + EXPECT_FLOAT_EQ(102.0f, NSMinX([view2 frame])); + EXPECT_FLOAT_EQ(0.0f, NSMinY([view2 frame])); + + EXPECT_FLOAT_EQ(150.0f, NSWidth([view3 frame])); + EXPECT_FLOAT_EQ(18.0f, NSHeight([view3 frame])); + EXPECT_FLOAT_EQ(0.0f, NSMinX([view3 frame])); + EXPECT_FLOAT_EQ(22.0f, NSMinY([view3 frame])); +} + +TEST_F(LayoutTest, PreferredHeightForEmptyLayout) { + SimpleGridLayout layout(NULL); + EXPECT_FLOAT_EQ(0.0f, layout.GetPreferredHeightForWidth(100.0)); +} + +TEST_F(LayoutTest, SimpleGridLayoutHeightForWidth) +{ + SimpleGridLayout layout(NULL); + layout.AddRow(); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + + scoped_nsobject<NSView> view1 = CreateViewWithHeight(22.0f); + layout.AddView(view1); + EXPECT_FLOAT_EQ(22.0f, layout.GetPreferredHeightForWidth(100.0)); + + layout.AddPaddingRow(12); + EXPECT_FLOAT_EQ(34.0f, layout.GetPreferredHeightForWidth(100.0)); + + layout.AddRow(); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + scoped_nsobject<NSView> view2 = CreateViewWithHeight(13.0f); + layout.AddView(view2); + EXPECT_FLOAT_EQ(47.0f, layout.GetPreferredHeightForWidth(100.0)); +} + +TEST_F(LayoutTest, SkipColumns) { + ColumnLayout layout(NULL); + + ASSERT_TRUE(layout.GetLastValidColumnSet() != NULL); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + layout.GetLastValidColumnSet()->AddColumn(0.4f); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + layout.GetLastValidColumnSet()->AddPaddingColumn(30); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + + // Skip a single column + EXPECT_EQ(0, layout.next_column()); + layout.SkipColumns(1); + EXPECT_EQ(1, layout.next_column()); + + // Skip multiple columns + layout.SkipColumns(2); + EXPECT_EQ(3, layout.next_column()); + + // SkipColumns skips padding, too + layout.SkipColumns(1); + EXPECT_EQ(5, layout.next_column()); +} + +TEST_F(LayoutTest, SkipPaddingColumns) { + ColumnLayout layout(NULL); + + ASSERT_TRUE(layout.GetLastValidColumnSet() != NULL); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + layout.GetLastValidColumnSet()->AddPaddingColumn(30); + layout.GetLastValidColumnSet()->AddColumn(0.4f); + layout.GetLastValidColumnSet()->AddPaddingColumn(30); + layout.GetLastValidColumnSet()->AddPaddingColumn(30); + layout.GetLastValidColumnSet()->AddColumn(0.4f); + + // Don't skip over non-padding columns. + EXPECT_EQ(0, layout.next_column()); + layout.SkipPaddingColumns(); + EXPECT_EQ(0, layout.next_column()); + + // Skip a single padding column. + layout.AdvanceColumn(); + EXPECT_EQ(1, layout.next_column()); + layout.SkipPaddingColumns(); + EXPECT_EQ(2, layout.next_column()); + + // Skip multiple padding columns. + layout.AdvanceColumn(); + EXPECT_EQ(3, layout.next_column()); + layout.SkipPaddingColumns(); + EXPECT_EQ(5, layout.next_column()); +} + +TEST_F(LayoutTest, SizeColumnsAdjustLocationAndSize) { + ColumnLayout layout(NULL); + + ASSERT_TRUE(layout.GetLastValidColumnSet() != NULL); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + layout.GetLastValidColumnSet()->AddPaddingColumn(30); + layout.GetLastValidColumnSet()->AddColumn(0.4f); + + layout.SizeRowsAndColumns(130.0f); + EXPECT_FLOAT_EQ(0.0f, layout.GetLastValidColumnSet()->ColumnLocation(0)); + EXPECT_FLOAT_EQ(60.0f, layout.GetLastValidColumnSet()->GetColumnWidth(0)); + EXPECT_FLOAT_EQ(60.0f, layout.GetLastValidColumnSet()->ColumnLocation(1)); + EXPECT_FLOAT_EQ(30.0f, layout.GetLastValidColumnSet()->GetColumnWidth(1)); + EXPECT_FLOAT_EQ(90.0f, layout.GetLastValidColumnSet()->ColumnLocation(2)); + EXPECT_FLOAT_EQ(40.0f, layout.GetLastValidColumnSet()->GetColumnWidth(2)); +} + +TEST_F(LayoutTest, AddViewSkipsPaddingColumns) { + ColumnLayout layout(NULL); + + ASSERT_TRUE(layout.GetLastValidColumnSet() != NULL); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + layout.GetLastValidColumnSet()->AddPaddingColumn(30); + layout.GetLastValidColumnSet()->AddColumn(0.4f); + + EXPECT_EQ(0, layout.next_column()); + scoped_nsobject<NSView> view1 = CreateViewWithWidth(37.0f); + layout.AddView(view1); + EXPECT_EQ(2, layout.next_column()); +} + +TEST_F(LayoutTest, ColumnLayoutAdjustsViews) { + ColumnLayout layout(NULL); + + ASSERT_TRUE(layout.GetLastValidColumnSet() != NULL); + layout.GetLastValidColumnSet()->AddColumn(0.6f); + layout.GetLastValidColumnSet()->AddPaddingColumn(30); + layout.GetLastValidColumnSet()->AddColumn(0.4f); + + scoped_nsobject<NSView> view1 = CreateViewWithWidth(37.0f); + scoped_nsobject<NSView> view2 = CreateViewWithWidth(42.0f); + layout.AddView(view1); + layout.AddView(view2); + + scoped_nsobject<NSView> host_view = CreateViewWithWidth(150.0f); + layout.Layout(host_view); + + EXPECT_FLOAT_EQ(72.0f, NSWidth([view1 frame])); + EXPECT_FLOAT_EQ(48.0f, NSWidth([view2 frame])); +} + +TEST_F(LayoutTest, ColumnSetAddPadding) { + ColumnSet columns(0); + + columns.AddPaddingColumn(20); + EXPECT_EQ(1, columns.num_columns()); + + columns.CalculateSize(20.0f); + EXPECT_FLOAT_EQ(20.0f, columns.GetColumnWidth(0)); + + columns.CalculateSize(120.0f); + EXPECT_FLOAT_EQ(20.0f, columns.GetColumnWidth(0)); +} + +TEST_F(LayoutTest, ColumnSetOneFlexibleColumn) { + ColumnSet columns(0); + + columns.AddColumn(1.0f); + EXPECT_EQ(1, columns.num_columns()); + + columns.CalculateSize(20.0f); + EXPECT_FLOAT_EQ(20.0f, columns.GetColumnWidth(0)); + + columns.CalculateSize(120.0f); + EXPECT_FLOAT_EQ(120.0f, columns.GetColumnWidth(0)); +} + +TEST_F(LayoutTest, ColumnSetTwoFlexibleColumns) { + ColumnSet columns(0); + + columns.AddColumn(0.6f); + columns.AddColumn(0.4f); + EXPECT_EQ(2, columns.num_columns()); + + columns.CalculateSize(100.0f); + EXPECT_FLOAT_EQ(60.0f, columns.GetColumnWidth(0)); + EXPECT_FLOAT_EQ(40.0f, columns.GetColumnWidth(1)); + + columns.CalculateSize(150.0f); + EXPECT_FLOAT_EQ(90.0f, columns.GetColumnWidth(0)); + EXPECT_FLOAT_EQ(60.0f, columns.GetColumnWidth(1)); +} + +TEST_F(LayoutTest, ColumnSetMixedColumns) { + ColumnSet columns(0); + + columns.AddColumn(0.6f); + columns.AddPaddingColumn(20); + columns.AddColumn(0.4f); + EXPECT_EQ(3, columns.num_columns()); + + columns.CalculateSize(100.0f); + EXPECT_FLOAT_EQ(48.0f, columns.GetColumnWidth(0)); + EXPECT_FLOAT_EQ(20.0f, columns.GetColumnWidth(1)); + EXPECT_FLOAT_EQ(32.0f, columns.GetColumnWidth(2)); + + columns.CalculateSize(200.0f); + EXPECT_FLOAT_EQ(108.0f, columns.GetColumnWidth(0)); + EXPECT_FLOAT_EQ(20.0f, columns.GetColumnWidth(1)); + EXPECT_FLOAT_EQ(72.0f, columns.GetColumnWidth(2)); +} + +} // namespace |