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