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