Home | History | Annotate | Download | only in controls
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "ui/views/controls/scroll_view.h"
      6 
      7 #include "base/logging.h"
      8 #include "ui/events/event.h"
      9 #include "ui/native_theme/native_theme.h"
     10 #include "ui/views/border.h"
     11 #include "ui/views/controls/scrollbar/native_scroll_bar.h"
     12 #include "ui/views/widget/root_view.h"
     13 
     14 namespace views {
     15 
     16 const char ScrollView::kViewClassName[] = "ScrollView";
     17 
     18 namespace {
     19 
     20 // Subclass of ScrollView that resets the border when the theme changes.
     21 class ScrollViewWithBorder : public views::ScrollView {
     22  public:
     23   ScrollViewWithBorder() {}
     24 
     25   // View overrides;
     26   virtual void OnNativeThemeChanged(const ui::NativeTheme* theme) OVERRIDE {
     27     SetBorder(Border::CreateSolidBorder(
     28         1,
     29         theme->GetSystemColor(ui::NativeTheme::kColorId_UnfocusedBorderColor)));
     30   }
     31 
     32  private:
     33   DISALLOW_COPY_AND_ASSIGN(ScrollViewWithBorder);
     34 };
     35 
     36 // Returns the position for the view so that it isn't scrolled off the visible
     37 // region.
     38 int CheckScrollBounds(int viewport_size, int content_size, int current_pos) {
     39   int max = std::max(content_size - viewport_size, 0);
     40   if (current_pos < 0)
     41     return 0;
     42   if (current_pos > max)
     43     return max;
     44   return current_pos;
     45 }
     46 
     47 // Make sure the content is not scrolled out of bounds
     48 void CheckScrollBounds(View* viewport, View* view) {
     49   if (!view)
     50     return;
     51 
     52   int x = CheckScrollBounds(viewport->width(), view->width(), -view->x());
     53   int y = CheckScrollBounds(viewport->height(), view->height(), -view->y());
     54 
     55   // This is no op if bounds are the same
     56   view->SetBounds(-x, -y, view->width(), view->height());
     57 }
     58 
     59 // Used by ScrollToPosition() to make sure the new position fits within the
     60 // allowed scroll range.
     61 int AdjustPosition(int current_position,
     62                    int new_position,
     63                    int content_size,
     64                    int viewport_size) {
     65   if (-current_position == new_position)
     66     return new_position;
     67   if (new_position < 0)
     68     return 0;
     69   const int max_position = std::max(0, content_size - viewport_size);
     70   return (new_position > max_position) ? max_position : new_position;
     71 }
     72 
     73 }  // namespace
     74 
     75 // Viewport contains the contents View of the ScrollView.
     76 class ScrollView::Viewport : public View {
     77  public:
     78   Viewport() {}
     79   virtual ~Viewport() {}
     80 
     81   virtual const char* GetClassName() const OVERRIDE {
     82     return "ScrollView::Viewport";
     83   }
     84 
     85   virtual void ScrollRectToVisible(const gfx::Rect& rect) OVERRIDE {
     86     if (!has_children() || !parent())
     87       return;
     88 
     89     View* contents = child_at(0);
     90     gfx::Rect scroll_rect(rect);
     91     scroll_rect.Offset(-contents->x(), -contents->y());
     92     static_cast<ScrollView*>(parent())->ScrollContentsRegionToBeVisible(
     93         scroll_rect);
     94   }
     95 
     96   virtual void ChildPreferredSizeChanged(View* child) OVERRIDE {
     97     if (parent())
     98       parent()->Layout();
     99   }
    100 
    101  private:
    102   DISALLOW_COPY_AND_ASSIGN(Viewport);
    103 };
    104 
    105 ScrollView::ScrollView()
    106     : contents_(NULL),
    107       contents_viewport_(new Viewport()),
    108       header_(NULL),
    109       header_viewport_(new Viewport()),
    110       horiz_sb_(new NativeScrollBar(true)),
    111       vert_sb_(new NativeScrollBar(false)),
    112       resize_corner_(NULL),
    113       min_height_(-1),
    114       max_height_(-1),
    115       hide_horizontal_scrollbar_(false) {
    116   set_notify_enter_exit_on_child(true);
    117 
    118   AddChildView(contents_viewport_);
    119   AddChildView(header_viewport_);
    120 
    121   // Don't add the scrollbars as children until we discover we need them
    122   // (ShowOrHideScrollBar).
    123   horiz_sb_->SetVisible(false);
    124   horiz_sb_->set_controller(this);
    125   vert_sb_->SetVisible(false);
    126   vert_sb_->set_controller(this);
    127   if (resize_corner_)
    128     resize_corner_->SetVisible(false);
    129 }
    130 
    131 ScrollView::~ScrollView() {
    132   // The scrollbars may not have been added, delete them to ensure they get
    133   // deleted.
    134   delete horiz_sb_;
    135   delete vert_sb_;
    136 
    137   if (resize_corner_ && !resize_corner_->parent())
    138     delete resize_corner_;
    139 }
    140 
    141 // static
    142 ScrollView* ScrollView::CreateScrollViewWithBorder() {
    143   return new ScrollViewWithBorder();
    144 }
    145 
    146 void ScrollView::SetContents(View* a_view) {
    147   SetHeaderOrContents(contents_viewport_, a_view, &contents_);
    148 }
    149 
    150 void ScrollView::SetHeader(View* header) {
    151   SetHeaderOrContents(header_viewport_, header, &header_);
    152 }
    153 
    154 gfx::Rect ScrollView::GetVisibleRect() const {
    155   if (!contents_)
    156     return gfx::Rect();
    157   return gfx::Rect(-contents_->x(), -contents_->y(),
    158                    contents_viewport_->width(), contents_viewport_->height());
    159 }
    160 
    161 void ScrollView::ClipHeightTo(int min_height, int max_height) {
    162   min_height_ = min_height;
    163   max_height_ = max_height;
    164 }
    165 
    166 int ScrollView::GetScrollBarWidth() const {
    167   return vert_sb_ ? vert_sb_->GetLayoutSize() : 0;
    168 }
    169 
    170 int ScrollView::GetScrollBarHeight() const {
    171   return horiz_sb_ ? horiz_sb_->GetLayoutSize() : 0;
    172 }
    173 
    174 void ScrollView::SetHorizontalScrollBar(ScrollBar* horiz_sb) {
    175   DCHECK(horiz_sb);
    176   horiz_sb->SetVisible(horiz_sb_->visible());
    177   delete horiz_sb_;
    178   horiz_sb->set_controller(this);
    179   horiz_sb_ = horiz_sb;
    180 }
    181 
    182 void ScrollView::SetVerticalScrollBar(ScrollBar* vert_sb) {
    183   DCHECK(vert_sb);
    184   vert_sb->SetVisible(vert_sb_->visible());
    185   delete vert_sb_;
    186   vert_sb->set_controller(this);
    187   vert_sb_ = vert_sb;
    188 }
    189 
    190 gfx::Size ScrollView::GetPreferredSize() const {
    191   if (!is_bounded())
    192     return View::GetPreferredSize();
    193 
    194   gfx::Size size = contents()->GetPreferredSize();
    195   size.SetToMax(gfx::Size(size.width(), min_height_));
    196   size.SetToMin(gfx::Size(size.width(), max_height_));
    197   gfx::Insets insets = GetInsets();
    198   size.Enlarge(insets.width(), insets.height());
    199   return size;
    200 }
    201 
    202 int ScrollView::GetHeightForWidth(int width) const {
    203   if (!is_bounded())
    204     return View::GetHeightForWidth(width);
    205 
    206   gfx::Insets insets = GetInsets();
    207   width = std::max(0, width - insets.width());
    208   int height = contents()->GetHeightForWidth(width) + insets.height();
    209   return std::min(std::max(height, min_height_), max_height_);
    210 }
    211 
    212 void ScrollView::Layout() {
    213   if (is_bounded()) {
    214     int content_width = width();
    215     int content_height = contents()->GetHeightForWidth(content_width);
    216     if (content_height > height()) {
    217       content_width = std::max(content_width - GetScrollBarWidth(), 0);
    218       content_height = contents()->GetHeightForWidth(content_width);
    219     }
    220     if (contents()->bounds().size() != gfx::Size(content_width, content_height))
    221       contents()->SetBounds(0, 0, content_width, content_height);
    222   }
    223 
    224   // Most views will want to auto-fit the available space. Most of them want to
    225   // use all available width (without overflowing) and only overflow in
    226   // height. Examples are HistoryView, MostVisitedView, DownloadTabView, etc.
    227   // Other views want to fit in both ways. An example is PrintView. To make both
    228   // happy, assume a vertical scrollbar but no horizontal scrollbar. To override
    229   // this default behavior, the inner view has to calculate the available space,
    230   // used ComputeScrollBarsVisibility() to use the same calculation that is done
    231   // here and sets its bound to fit within.
    232   gfx::Rect viewport_bounds = GetContentsBounds();
    233   const int contents_x = viewport_bounds.x();
    234   const int contents_y = viewport_bounds.y();
    235   if (viewport_bounds.IsEmpty()) {
    236     // There's nothing to layout.
    237     return;
    238   }
    239 
    240   const int header_height =
    241       std::min(viewport_bounds.height(),
    242                header_ ? header_->GetPreferredSize().height() : 0);
    243   viewport_bounds.set_height(
    244       std::max(0, viewport_bounds.height() - header_height));
    245   viewport_bounds.set_y(viewport_bounds.y() + header_height);
    246   // viewport_size is the total client space available.
    247   gfx::Size viewport_size = viewport_bounds.size();
    248   // Assumes a vertical scrollbar since most of the current views are designed
    249   // for this.
    250   int horiz_sb_height = GetScrollBarHeight();
    251   int vert_sb_width = GetScrollBarWidth();
    252   viewport_bounds.set_width(viewport_bounds.width() - vert_sb_width);
    253   // Update the bounds right now so the inner views can fit in it.
    254   contents_viewport_->SetBoundsRect(viewport_bounds);
    255 
    256   // Give |contents_| a chance to update its bounds if it depends on the
    257   // viewport.
    258   if (contents_)
    259     contents_->Layout();
    260 
    261   bool should_layout_contents = false;
    262   bool horiz_sb_required = false;
    263   bool vert_sb_required = false;
    264   if (contents_) {
    265     gfx::Size content_size = contents_->size();
    266     ComputeScrollBarsVisibility(viewport_size,
    267                                 content_size,
    268                                 &horiz_sb_required,
    269                                 &vert_sb_required);
    270   }
    271   bool resize_corner_required = resize_corner_ && horiz_sb_required &&
    272                                 vert_sb_required;
    273   // Take action.
    274   SetControlVisibility(horiz_sb_, horiz_sb_required);
    275   SetControlVisibility(vert_sb_, vert_sb_required);
    276   SetControlVisibility(resize_corner_, resize_corner_required);
    277 
    278   // Non-default.
    279   if (horiz_sb_required) {
    280     viewport_bounds.set_height(
    281         std::max(0, viewport_bounds.height() - horiz_sb_height));
    282     should_layout_contents = true;
    283   }
    284   // Default.
    285   if (!vert_sb_required) {
    286     viewport_bounds.set_width(viewport_bounds.width() + vert_sb_width);
    287     should_layout_contents = true;
    288   }
    289 
    290   if (horiz_sb_required) {
    291     int height_offset = horiz_sb_->GetContentOverlapSize();
    292     horiz_sb_->SetBounds(0,
    293                          viewport_bounds.bottom() - height_offset,
    294                          viewport_bounds.right(),
    295                          horiz_sb_height + height_offset);
    296   }
    297   if (vert_sb_required) {
    298     int width_offset = vert_sb_->GetContentOverlapSize();
    299     vert_sb_->SetBounds(viewport_bounds.right() - width_offset,
    300                         0,
    301                         vert_sb_width + width_offset,
    302                         viewport_bounds.bottom());
    303   }
    304   if (resize_corner_required) {
    305     // Show the resize corner.
    306     resize_corner_->SetBounds(viewport_bounds.right(),
    307                               viewport_bounds.bottom(),
    308                               vert_sb_width,
    309                               horiz_sb_height);
    310   }
    311 
    312   // Update to the real client size with the visible scrollbars.
    313   contents_viewport_->SetBoundsRect(viewport_bounds);
    314   if (should_layout_contents && contents_)
    315     contents_->Layout();
    316 
    317   header_viewport_->SetBounds(contents_x, contents_y,
    318                               viewport_bounds.width(), header_height);
    319   if (header_)
    320     header_->Layout();
    321 
    322   CheckScrollBounds(header_viewport_, header_);
    323   CheckScrollBounds(contents_viewport_, contents_);
    324   SchedulePaint();
    325   UpdateScrollBarPositions();
    326 }
    327 
    328 bool ScrollView::OnKeyPressed(const ui::KeyEvent& event) {
    329   bool processed = false;
    330 
    331   // Give vertical scrollbar priority
    332   if (vert_sb_->visible())
    333     processed = vert_sb_->OnKeyPressed(event);
    334 
    335   if (!processed && horiz_sb_->visible())
    336     processed = horiz_sb_->OnKeyPressed(event);
    337 
    338   return processed;
    339 }
    340 
    341 bool ScrollView::OnMouseWheel(const ui::MouseWheelEvent& e) {
    342   bool processed = false;
    343   // Give vertical scrollbar priority
    344   if (vert_sb_->visible())
    345     processed = vert_sb_->OnMouseWheel(e);
    346 
    347   if (!processed && horiz_sb_->visible())
    348     processed = horiz_sb_->OnMouseWheel(e);
    349 
    350   return processed;
    351 }
    352 
    353 void ScrollView::OnMouseEntered(const ui::MouseEvent& event) {
    354   if (horiz_sb_)
    355     horiz_sb_->OnMouseEnteredScrollView(event);
    356   if (vert_sb_)
    357     vert_sb_->OnMouseEnteredScrollView(event);
    358 }
    359 
    360 void ScrollView::OnMouseExited(const ui::MouseEvent& event) {
    361   if (horiz_sb_)
    362     horiz_sb_->OnMouseExitedScrollView(event);
    363   if (vert_sb_)
    364     vert_sb_->OnMouseExitedScrollView(event);
    365 }
    366 
    367 void ScrollView::OnGestureEvent(ui::GestureEvent* event) {
    368   // If the event happened on one of the scrollbars, then those events are
    369   // sent directly to the scrollbars. Otherwise, only scroll events are sent to
    370   // the scrollbars.
    371   bool scroll_event = event->type() == ui::ET_GESTURE_SCROLL_UPDATE ||
    372                       event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
    373                       event->type() == ui::ET_GESTURE_SCROLL_END ||
    374                       event->type() == ui::ET_SCROLL_FLING_START;
    375 
    376   if (vert_sb_->visible()) {
    377     if (vert_sb_->bounds().Contains(event->location()) || scroll_event)
    378       vert_sb_->OnGestureEvent(event);
    379   }
    380   if (!event->handled() && horiz_sb_->visible()) {
    381     if (horiz_sb_->bounds().Contains(event->location()) || scroll_event)
    382       horiz_sb_->OnGestureEvent(event);
    383   }
    384 }
    385 
    386 const char* ScrollView::GetClassName() const {
    387   return kViewClassName;
    388 }
    389 
    390 void ScrollView::ScrollToPosition(ScrollBar* source, int position) {
    391   if (!contents_)
    392     return;
    393 
    394   if (source == horiz_sb_ && horiz_sb_->visible()) {
    395     position = AdjustPosition(contents_->x(), position, contents_->width(),
    396                               contents_viewport_->width());
    397     if (-contents_->x() == position)
    398       return;
    399     contents_->SetX(-position);
    400     if (header_) {
    401       header_->SetX(-position);
    402       header_->SchedulePaintInRect(header_->GetVisibleBounds());
    403     }
    404   } else if (source == vert_sb_ && vert_sb_->visible()) {
    405     position = AdjustPosition(contents_->y(), position, contents_->height(),
    406                               contents_viewport_->height());
    407     if (-contents_->y() == position)
    408       return;
    409     contents_->SetY(-position);
    410   }
    411   contents_->SchedulePaintInRect(contents_->GetVisibleBounds());
    412 }
    413 
    414 int ScrollView::GetScrollIncrement(ScrollBar* source, bool is_page,
    415                                    bool is_positive) {
    416   bool is_horizontal = source->IsHorizontal();
    417   int amount = 0;
    418   if (contents_) {
    419     if (is_page) {
    420       amount = contents_->GetPageScrollIncrement(
    421           this, is_horizontal, is_positive);
    422     } else {
    423       amount = contents_->GetLineScrollIncrement(
    424           this, is_horizontal, is_positive);
    425     }
    426     if (amount > 0)
    427       return amount;
    428   }
    429   // No view, or the view didn't return a valid amount.
    430   if (is_page) {
    431     return is_horizontal ? contents_viewport_->width() :
    432                            contents_viewport_->height();
    433   }
    434   return is_horizontal ? contents_viewport_->width() / 5 :
    435                          contents_viewport_->height() / 5;
    436 }
    437 
    438 void ScrollView::SetHeaderOrContents(View* parent,
    439                                      View* new_view,
    440                                      View** member) {
    441   if (*member == new_view)
    442     return;
    443 
    444   delete *member;
    445   *member = new_view;
    446   if (*member)
    447     parent->AddChildView(*member);
    448   Layout();
    449 }
    450 
    451 void ScrollView::ScrollContentsRegionToBeVisible(const gfx::Rect& rect) {
    452   if (!contents_ || (!horiz_sb_->visible() && !vert_sb_->visible()))
    453     return;
    454 
    455   // Figure out the maximums for this scroll view.
    456   const int contents_max_x =
    457       std::max(contents_viewport_->width(), contents_->width());
    458   const int contents_max_y =
    459       std::max(contents_viewport_->height(), contents_->height());
    460 
    461   // Make sure x and y are within the bounds of [0,contents_max_*].
    462   int x = std::max(0, std::min(contents_max_x, rect.x()));
    463   int y = std::max(0, std::min(contents_max_y, rect.y()));
    464 
    465   // Figure out how far and down the rectangle will go taking width
    466   // and height into account.  This will be "clipped" by the viewport.
    467   const int max_x = std::min(contents_max_x,
    468       x + std::min(rect.width(), contents_viewport_->width()));
    469   const int max_y = std::min(contents_max_y,
    470       y + std::min(rect.height(), contents_viewport_->height()));
    471 
    472   // See if the rect is already visible. Note the width is (max_x - x)
    473   // and the height is (max_y - y) to take into account the clipping of
    474   // either viewport or the content size.
    475   const gfx::Rect vis_rect = GetVisibleRect();
    476   if (vis_rect.Contains(gfx::Rect(x, y, max_x - x, max_y - y)))
    477     return;
    478 
    479   // Shift contents_'s X and Y so that the region is visible. If we
    480   // need to shift up or left from where we currently are then we need
    481   // to get it so that the content appears in the upper/left
    482   // corner. This is done by setting the offset to -X or -Y.  For down
    483   // or right shifts we need to make sure it appears in the
    484   // lower/right corner. This is calculated by taking max_x or max_y
    485   // and scaling it back by the size of the viewport.
    486   const int new_x =
    487       (vis_rect.x() > x) ? x : std::max(0, max_x - contents_viewport_->width());
    488   const int new_y =
    489       (vis_rect.y() > y) ? y : std::max(0, max_y -
    490                                         contents_viewport_->height());
    491 
    492   contents_->SetX(-new_x);
    493   if (header_)
    494     header_->SetX(-new_x);
    495   contents_->SetY(-new_y);
    496   UpdateScrollBarPositions();
    497 }
    498 
    499 void ScrollView::ComputeScrollBarsVisibility(const gfx::Size& vp_size,
    500                                              const gfx::Size& content_size,
    501                                              bool* horiz_is_shown,
    502                                              bool* vert_is_shown) const {
    503   // Try to fit both ways first, then try vertical bar only, then horizontal
    504   // bar only, then defaults to both shown.
    505   if (content_size.width() <= vp_size.width() &&
    506       content_size.height() <= vp_size.height()) {
    507     *horiz_is_shown = false;
    508     *vert_is_shown = false;
    509   } else if (content_size.width() <= vp_size.width() - GetScrollBarWidth()) {
    510     *horiz_is_shown = false;
    511     *vert_is_shown = true;
    512   } else if (content_size.height() <= vp_size.height() - GetScrollBarHeight()) {
    513     *horiz_is_shown = true;
    514     *vert_is_shown = false;
    515   } else {
    516     *horiz_is_shown = true;
    517     *vert_is_shown = true;
    518   }
    519 
    520   if (hide_horizontal_scrollbar_)
    521     *horiz_is_shown = false;
    522 }
    523 
    524 // Make sure that a single scrollbar is created and visible as needed
    525 void ScrollView::SetControlVisibility(View* control, bool should_show) {
    526   if (!control)
    527     return;
    528   if (should_show) {
    529     if (!control->visible()) {
    530       AddChildView(control);
    531       control->SetVisible(true);
    532     }
    533   } else {
    534     RemoveChildView(control);
    535     control->SetVisible(false);
    536   }
    537 }
    538 
    539 void ScrollView::UpdateScrollBarPositions() {
    540   if (!contents_)
    541     return;
    542 
    543   if (horiz_sb_->visible()) {
    544     int vw = contents_viewport_->width();
    545     int cw = contents_->width();
    546     int origin = contents_->x();
    547     horiz_sb_->Update(vw, cw, -origin);
    548   }
    549   if (vert_sb_->visible()) {
    550     int vh = contents_viewport_->height();
    551     int ch = contents_->height();
    552     int origin = contents_->y();
    553     vert_sb_->Update(vh, ch, -origin);
    554   }
    555 }
    556 
    557 // VariableRowHeightScrollHelper ----------------------------------------------
    558 
    559 VariableRowHeightScrollHelper::VariableRowHeightScrollHelper(
    560     Controller* controller) : controller_(controller) {
    561 }
    562 
    563 VariableRowHeightScrollHelper::~VariableRowHeightScrollHelper() {
    564 }
    565 
    566 int VariableRowHeightScrollHelper::GetPageScrollIncrement(
    567     ScrollView* scroll_view, bool is_horizontal, bool is_positive) {
    568   if (is_horizontal)
    569     return 0;
    570   // y coordinate is most likely negative.
    571   int y = abs(scroll_view->contents()->y());
    572   int vis_height = scroll_view->contents()->parent()->height();
    573   if (is_positive) {
    574     // Align the bottom most row to the top of the view.
    575     int bottom = std::min(scroll_view->contents()->height() - 1,
    576                           y + vis_height);
    577     RowInfo bottom_row_info = GetRowInfo(bottom);
    578     // If 0, ScrollView will provide a default value.
    579     return std::max(0, bottom_row_info.origin - y);
    580   } else {
    581     // Align the row on the previous page to to the top of the view.
    582     int last_page_y = y - vis_height;
    583     RowInfo last_page_info = GetRowInfo(std::max(0, last_page_y));
    584     if (last_page_y != last_page_info.origin)
    585       return std::max(0, y - last_page_info.origin - last_page_info.height);
    586     return std::max(0, y - last_page_info.origin);
    587   }
    588 }
    589 
    590 int VariableRowHeightScrollHelper::GetLineScrollIncrement(
    591     ScrollView* scroll_view, bool is_horizontal, bool is_positive) {
    592   if (is_horizontal)
    593     return 0;
    594   // y coordinate is most likely negative.
    595   int y = abs(scroll_view->contents()->y());
    596   RowInfo row = GetRowInfo(y);
    597   if (is_positive) {
    598     return row.height - (y - row.origin);
    599   } else if (y == row.origin) {
    600     row = GetRowInfo(std::max(0, row.origin - 1));
    601     return y - row.origin;
    602   } else {
    603     return y - row.origin;
    604   }
    605 }
    606 
    607 VariableRowHeightScrollHelper::RowInfo
    608     VariableRowHeightScrollHelper::GetRowInfo(int y) {
    609   return controller_->GetRowInfo(y);
    610 }
    611 
    612 // FixedRowHeightScrollHelper -----------------------------------------------
    613 
    614 FixedRowHeightScrollHelper::FixedRowHeightScrollHelper(int top_margin,
    615                                                        int row_height)
    616     : VariableRowHeightScrollHelper(NULL),
    617       top_margin_(top_margin),
    618       row_height_(row_height) {
    619   DCHECK_GT(row_height, 0);
    620 }
    621 
    622 VariableRowHeightScrollHelper::RowInfo
    623     FixedRowHeightScrollHelper::GetRowInfo(int y) {
    624   if (y < top_margin_)
    625     return RowInfo(0, top_margin_);
    626   return RowInfo((y - top_margin_) / row_height_ * row_height_ + top_margin_,
    627                  row_height_);
    628 }
    629 
    630 }  // namespace views
    631