| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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 "views/controls/scroll_view.h" | |
| 6 | |
| 7 #include "base/logging.h" | |
| 8 #include "ui/views/controls/scrollbar/native_scroll_bar.h" | |
| 9 #include "ui/views/widget/root_view.h" | |
| 10 | |
| 11 namespace views { | |
| 12 | |
| 13 const char* const ScrollView::kViewClassName = "views/ScrollView"; | |
| 14 | |
| 15 // Viewport contains the contents View of the ScrollView. | |
| 16 class Viewport : public View { | |
| 17 public: | |
| 18 Viewport() {} | |
| 19 virtual ~Viewport() {} | |
| 20 | |
| 21 virtual std::string GetClassName() const { | |
| 22 return "views/Viewport"; | |
| 23 } | |
| 24 | |
| 25 virtual void ScrollRectToVisible(const gfx::Rect& rect) { | |
| 26 if (!has_children() || !parent()) | |
| 27 return; | |
| 28 | |
| 29 View* contents = child_at(0); | |
| 30 gfx::Rect scroll_rect(rect); | |
| 31 scroll_rect.Offset(-contents->x(), -contents->y()); | |
| 32 static_cast<ScrollView*>(parent())->ScrollContentsRegionToBeVisible( | |
| 33 scroll_rect); | |
| 34 } | |
| 35 | |
| 36 private: | |
| 37 DISALLOW_COPY_AND_ASSIGN(Viewport); | |
| 38 }; | |
| 39 | |
| 40 | |
| 41 ScrollView::ScrollView() { | |
| 42 Init(new NativeScrollBar(true), new NativeScrollBar(false), NULL); | |
| 43 } | |
| 44 | |
| 45 ScrollView::ScrollView(ScrollBar* horizontal_scrollbar, | |
| 46 ScrollBar* vertical_scrollbar, | |
| 47 View* resize_corner) { | |
| 48 Init(horizontal_scrollbar, vertical_scrollbar, resize_corner); | |
| 49 } | |
| 50 | |
| 51 ScrollView::~ScrollView() { | |
| 52 // If scrollbars are currently not used, delete them | |
| 53 if (!horiz_sb_->parent()) | |
| 54 delete horiz_sb_; | |
| 55 | |
| 56 if (!vert_sb_->parent()) | |
| 57 delete vert_sb_; | |
| 58 | |
| 59 if (resize_corner_ && !resize_corner_->parent()) | |
| 60 delete resize_corner_; | |
| 61 } | |
| 62 | |
| 63 void ScrollView::SetContents(View* a_view) { | |
| 64 if (contents_ && contents_ != a_view) { | |
| 65 viewport_->RemoveChildView(contents_); | |
| 66 delete contents_; | |
| 67 contents_ = NULL; | |
| 68 } | |
| 69 | |
| 70 if (a_view) { | |
| 71 contents_ = a_view; | |
| 72 viewport_->AddChildView(contents_); | |
| 73 } | |
| 74 | |
| 75 Layout(); | |
| 76 } | |
| 77 | |
| 78 View* ScrollView::GetContents() const { | |
| 79 return contents_; | |
| 80 } | |
| 81 | |
| 82 void ScrollView::Init(ScrollBar* horizontal_scrollbar, | |
| 83 ScrollBar* vertical_scrollbar, | |
| 84 View* resize_corner) { | |
| 85 DCHECK(horizontal_scrollbar && vertical_scrollbar); | |
| 86 | |
| 87 contents_ = NULL; | |
| 88 horiz_sb_ = horizontal_scrollbar; | |
| 89 vert_sb_ = vertical_scrollbar; | |
| 90 resize_corner_ = resize_corner; | |
| 91 | |
| 92 viewport_ = new Viewport(); | |
| 93 AddChildView(viewport_); | |
| 94 | |
| 95 // Don't add the scrollbars as children until we discover we need them | |
| 96 // (ShowOrHideScrollBar). | |
| 97 horiz_sb_->SetVisible(false); | |
| 98 horiz_sb_->set_controller(this); | |
| 99 vert_sb_->SetVisible(false); | |
| 100 vert_sb_->set_controller(this); | |
| 101 if (resize_corner_) | |
| 102 resize_corner_->SetVisible(false); | |
| 103 } | |
| 104 | |
| 105 // Make sure that a single scrollbar is created and visible as needed | |
| 106 void ScrollView::SetControlVisibility(View* control, bool should_show) { | |
| 107 if (!control) | |
| 108 return; | |
| 109 if (should_show) { | |
| 110 if (!control->IsVisible()) { | |
| 111 AddChildView(control); | |
| 112 control->SetVisible(true); | |
| 113 } | |
| 114 } else { | |
| 115 RemoveChildView(control); | |
| 116 control->SetVisible(false); | |
| 117 } | |
| 118 } | |
| 119 | |
| 120 void ScrollView::ComputeScrollBarsVisibility(const gfx::Size& vp_size, | |
| 121 const gfx::Size& content_size, | |
| 122 bool* horiz_is_shown, | |
| 123 bool* vert_is_shown) const { | |
| 124 // Try to fit both ways first, then try vertical bar only, then horizontal | |
| 125 // bar only, then defaults to both shown. | |
| 126 if (content_size.width() <= vp_size.width() && | |
| 127 content_size.height() <= vp_size.height()) { | |
| 128 *horiz_is_shown = false; | |
| 129 *vert_is_shown = false; | |
| 130 } else if (content_size.width() <= vp_size.width() - GetScrollBarWidth()) { | |
| 131 *horiz_is_shown = false; | |
| 132 *vert_is_shown = true; | |
| 133 } else if (content_size.height() <= vp_size.height() - GetScrollBarHeight()) { | |
| 134 *horiz_is_shown = true; | |
| 135 *vert_is_shown = false; | |
| 136 } else { | |
| 137 *horiz_is_shown = true; | |
| 138 *vert_is_shown = true; | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 void ScrollView::Layout() { | |
| 143 // Most views will want to auto-fit the available space. Most of them want to | |
| 144 // use the all available width (without overflowing) and only overflow in | |
| 145 // height. Examples are HistoryView, MostVisitedView, DownloadTabView, etc. | |
| 146 // Other views want to fit in both ways. An example is PrintView. To make both | |
| 147 // happy, assume a vertical scrollbar but no horizontal scrollbar. To | |
| 148 // override this default behavior, the inner view has to calculate the | |
| 149 // available space, used ComputeScrollBarsVisibility() to use the same | |
| 150 // calculation that is done here and sets its bound to fit within. | |
| 151 gfx::Rect viewport_bounds = GetLocalBounds(); | |
| 152 // Realign it to 0 so it can be used as-is for SetBounds(). | |
| 153 viewport_bounds.set_origin(gfx::Point(0, 0)); | |
| 154 // viewport_size is the total client space available. | |
| 155 gfx::Size viewport_size = viewport_bounds.size(); | |
| 156 if (viewport_bounds.IsEmpty()) { | |
| 157 // There's nothing to layout. | |
| 158 return; | |
| 159 } | |
| 160 | |
| 161 // Assumes a vertical scrollbar since most the current views are designed for | |
| 162 // this. | |
| 163 int horiz_sb_height = GetScrollBarHeight(); | |
| 164 int vert_sb_width = GetScrollBarWidth(); | |
| 165 viewport_bounds.set_width(viewport_bounds.width() - vert_sb_width); | |
| 166 // Update the bounds right now so the inner views can fit in it. | |
| 167 viewport_->SetBoundsRect(viewport_bounds); | |
| 168 | |
| 169 // Give contents_ a chance to update its bounds if it depends on the | |
| 170 // viewport. | |
| 171 if (contents_) | |
| 172 contents_->Layout(); | |
| 173 | |
| 174 bool should_layout_contents = false; | |
| 175 bool horiz_sb_required = false; | |
| 176 bool vert_sb_required = false; | |
| 177 if (contents_) { | |
| 178 gfx::Size content_size = contents_->size(); | |
| 179 ComputeScrollBarsVisibility(viewport_size, | |
| 180 content_size, | |
| 181 &horiz_sb_required, | |
| 182 &vert_sb_required); | |
| 183 } | |
| 184 bool resize_corner_required = resize_corner_ && horiz_sb_required && | |
| 185 vert_sb_required; | |
| 186 // Take action. | |
| 187 SetControlVisibility(horiz_sb_, horiz_sb_required); | |
| 188 SetControlVisibility(vert_sb_, vert_sb_required); | |
| 189 SetControlVisibility(resize_corner_, resize_corner_required); | |
| 190 | |
| 191 // Non-default. | |
| 192 if (horiz_sb_required) { | |
| 193 viewport_bounds.set_height( | |
| 194 std::max(0, viewport_bounds.height() - horiz_sb_height)); | |
| 195 should_layout_contents = true; | |
| 196 } | |
| 197 // Default. | |
| 198 if (!vert_sb_required) { | |
| 199 viewport_bounds.set_width(viewport_bounds.width() + vert_sb_width); | |
| 200 should_layout_contents = true; | |
| 201 } | |
| 202 | |
| 203 if (horiz_sb_required) { | |
| 204 horiz_sb_->SetBounds(0, | |
| 205 viewport_bounds.bottom(), | |
| 206 viewport_bounds.right(), | |
| 207 horiz_sb_height); | |
| 208 } | |
| 209 if (vert_sb_required) { | |
| 210 vert_sb_->SetBounds(viewport_bounds.right(), | |
| 211 0, | |
| 212 vert_sb_width, | |
| 213 viewport_bounds.bottom()); | |
| 214 } | |
| 215 if (resize_corner_required) { | |
| 216 // Show the resize corner. | |
| 217 resize_corner_->SetBounds(viewport_bounds.right(), | |
| 218 viewport_bounds.bottom(), | |
| 219 vert_sb_width, | |
| 220 horiz_sb_height); | |
| 221 } | |
| 222 | |
| 223 // Update to the real client size with the visible scrollbars. | |
| 224 viewport_->SetBoundsRect(viewport_bounds); | |
| 225 if (should_layout_contents && contents_) | |
| 226 contents_->Layout(); | |
| 227 | |
| 228 CheckScrollBounds(); | |
| 229 SchedulePaint(); | |
| 230 UpdateScrollBarPositions(); | |
| 231 } | |
| 232 | |
| 233 int ScrollView::CheckScrollBounds(int viewport_size, | |
| 234 int content_size, | |
| 235 int current_pos) { | |
| 236 int max = std::max(content_size - viewport_size, 0); | |
| 237 if (current_pos < 0) | |
| 238 current_pos = 0; | |
| 239 else if (current_pos > max) | |
| 240 current_pos = max; | |
| 241 return current_pos; | |
| 242 } | |
| 243 | |
| 244 void ScrollView::CheckScrollBounds() { | |
| 245 if (contents_) { | |
| 246 int x, y; | |
| 247 | |
| 248 x = CheckScrollBounds(viewport_->width(), | |
| 249 contents_->width(), | |
| 250 -contents_->x()); | |
| 251 y = CheckScrollBounds(viewport_->height(), | |
| 252 contents_->height(), | |
| 253 -contents_->y()); | |
| 254 | |
| 255 // This is no op if bounds are the same | |
| 256 contents_->SetBounds(-x, -y, contents_->width(), contents_->height()); | |
| 257 } | |
| 258 } | |
| 259 | |
| 260 gfx::Rect ScrollView::GetVisibleRect() const { | |
| 261 if (!contents_) | |
| 262 return gfx::Rect(); | |
| 263 | |
| 264 const int x = | |
| 265 (horiz_sb_ && horiz_sb_->IsVisible()) ? horiz_sb_->GetPosition() : 0; | |
| 266 const int y = | |
| 267 (vert_sb_ && vert_sb_->IsVisible()) ? vert_sb_->GetPosition() : 0; | |
| 268 return gfx::Rect(x, y, viewport_->width(), viewport_->height()); | |
| 269 } | |
| 270 | |
| 271 void ScrollView::ScrollContentsRegionToBeVisible(const gfx::Rect& rect) { | |
| 272 if (!contents_ || ((!horiz_sb_ || !horiz_sb_->IsVisible()) && | |
| 273 (!vert_sb_ || !vert_sb_->IsVisible()))) { | |
| 274 return; | |
| 275 } | |
| 276 | |
| 277 // Figure out the maximums for this scroll view. | |
| 278 const int contents_max_x = | |
| 279 std::max(viewport_->width(), contents_->width()); | |
| 280 const int contents_max_y = | |
| 281 std::max(viewport_->height(), contents_->height()); | |
| 282 | |
| 283 // Make sure x and y are within the bounds of [0,contents_max_*]. | |
| 284 int x = std::max(0, std::min(contents_max_x, rect.x())); | |
| 285 int y = std::max(0, std::min(contents_max_y, rect.y())); | |
| 286 | |
| 287 // Figure out how far and down the rectangle will go taking width | |
| 288 // and height into account. This will be "clipped" by the viewport. | |
| 289 const int max_x = std::min(contents_max_x, | |
| 290 x + std::min(rect.width(), viewport_->width())); | |
| 291 const int max_y = std::min(contents_max_y, | |
| 292 y + std::min(rect.height(), viewport_->height())); | |
| 293 | |
| 294 // See if the rect is already visible. Note the width is (max_x - x) | |
| 295 // and the height is (max_y - y) to take into account the clipping of | |
| 296 // either viewport or the content size. | |
| 297 const gfx::Rect vis_rect = GetVisibleRect(); | |
| 298 if (vis_rect.Contains(gfx::Rect(x, y, max_x - x, max_y - y))) | |
| 299 return; | |
| 300 | |
| 301 // Shift contents_'s X and Y so that the region is visible. If we | |
| 302 // need to shift up or left from where we currently are then we need | |
| 303 // to get it so that the content appears in the upper/left | |
| 304 // corner. This is done by setting the offset to -X or -Y. For down | |
| 305 // or right shifts we need to make sure it appears in the | |
| 306 // lower/right corner. This is calculated by taking max_x or max_y | |
| 307 // and scaling it back by the size of the viewport. | |
| 308 const int new_x = | |
| 309 (vis_rect.x() > x) ? x : std::max(0, max_x - viewport_->width()); | |
| 310 const int new_y = | |
| 311 (vis_rect.y() > y) ? y : std::max(0, max_y - viewport_->height()); | |
| 312 | |
| 313 contents_->SetX(-new_x); | |
| 314 contents_->SetY(-new_y); | |
| 315 UpdateScrollBarPositions(); | |
| 316 } | |
| 317 | |
| 318 void ScrollView::UpdateScrollBarPositions() { | |
| 319 if (!contents_) { | |
| 320 return; | |
| 321 } | |
| 322 | |
| 323 if (horiz_sb_->IsVisible()) { | |
| 324 int vw = viewport_->width(); | |
| 325 int cw = contents_->width(); | |
| 326 int origin = contents_->x(); | |
| 327 horiz_sb_->Update(vw, cw, -origin); | |
| 328 } | |
| 329 if (vert_sb_->IsVisible()) { | |
| 330 int vh = viewport_->height(); | |
| 331 int ch = contents_->height(); | |
| 332 int origin = contents_->y(); | |
| 333 vert_sb_->Update(vh, ch, -origin); | |
| 334 } | |
| 335 } | |
| 336 | |
| 337 // TODO(ACW): We should really use ScrollWindowEx as needed | |
| 338 void ScrollView::ScrollToPosition(ScrollBar* source, int position) { | |
| 339 if (!contents_) | |
| 340 return; | |
| 341 | |
| 342 if (source == horiz_sb_ && horiz_sb_->IsVisible()) { | |
| 343 int vw = viewport_->width(); | |
| 344 int cw = contents_->width(); | |
| 345 int origin = contents_->x(); | |
| 346 if (-origin != position) { | |
| 347 int max_pos = std::max(0, cw - vw); | |
| 348 if (position < 0) | |
| 349 position = 0; | |
| 350 else if (position > max_pos) | |
| 351 position = max_pos; | |
| 352 contents_->SetX(-position); | |
| 353 contents_->SchedulePaintInRect(contents_->GetVisibleBounds()); | |
| 354 } | |
| 355 } else if (source == vert_sb_ && vert_sb_->IsVisible()) { | |
| 356 int vh = viewport_->height(); | |
| 357 int ch = contents_->height(); | |
| 358 int origin = contents_->y(); | |
| 359 if (-origin != position) { | |
| 360 int max_pos = std::max(0, ch - vh); | |
| 361 if (position < 0) | |
| 362 position = 0; | |
| 363 else if (position > max_pos) | |
| 364 position = max_pos; | |
| 365 contents_->SetY(-position); | |
| 366 contents_->SchedulePaintInRect(contents_->GetVisibleBounds()); | |
| 367 } | |
| 368 } | |
| 369 } | |
| 370 | |
| 371 int ScrollView::GetScrollIncrement(ScrollBar* source, bool is_page, | |
| 372 bool is_positive) { | |
| 373 bool is_horizontal = source->IsHorizontal(); | |
| 374 int amount = 0; | |
| 375 View* view = GetContents(); | |
| 376 if (view) { | |
| 377 if (is_page) | |
| 378 amount = view->GetPageScrollIncrement(this, is_horizontal, is_positive); | |
| 379 else | |
| 380 amount = view->GetLineScrollIncrement(this, is_horizontal, is_positive); | |
| 381 if (amount > 0) | |
| 382 return amount; | |
| 383 } | |
| 384 // No view, or the view didn't return a valid amount. | |
| 385 if (is_page) | |
| 386 return is_horizontal ? viewport_->width() : viewport_->height(); | |
| 387 return is_horizontal ? viewport_->width() / 5 : viewport_->height() / 5; | |
| 388 } | |
| 389 | |
| 390 bool ScrollView::OnKeyPressed(const KeyEvent& event) { | |
| 391 bool processed = false; | |
| 392 | |
| 393 // Give vertical scrollbar priority | |
| 394 if (vert_sb_->IsVisible()) { | |
| 395 processed = vert_sb_->OnKeyPressed(event); | |
| 396 } | |
| 397 | |
| 398 if (!processed && horiz_sb_->IsVisible()) { | |
| 399 processed = horiz_sb_->OnKeyPressed(event); | |
| 400 } | |
| 401 return processed; | |
| 402 } | |
| 403 | |
| 404 bool ScrollView::OnMouseWheel(const MouseWheelEvent& e) { | |
| 405 bool processed = false; | |
| 406 // Give vertical scrollbar priority | |
| 407 if (vert_sb_->IsVisible()) { | |
| 408 processed = vert_sb_->OnMouseWheel(e); | |
| 409 } | |
| 410 if (!processed && horiz_sb_->IsVisible()) { | |
| 411 processed = horiz_sb_->OnMouseWheel(e); | |
| 412 } | |
| 413 return processed; | |
| 414 } | |
| 415 | |
| 416 std::string ScrollView::GetClassName() const { | |
| 417 return kViewClassName; | |
| 418 } | |
| 419 | |
| 420 int ScrollView::GetScrollBarWidth() const { | |
| 421 return vert_sb_->GetLayoutSize(); | |
| 422 } | |
| 423 | |
| 424 int ScrollView::GetScrollBarHeight() const { | |
| 425 return horiz_sb_->GetLayoutSize(); | |
| 426 } | |
| 427 | |
| 428 // VariableRowHeightScrollHelper ---------------------------------------------- | |
| 429 | |
| 430 VariableRowHeightScrollHelper::VariableRowHeightScrollHelper( | |
| 431 Controller* controller) : controller_(controller) { | |
| 432 } | |
| 433 | |
| 434 VariableRowHeightScrollHelper::~VariableRowHeightScrollHelper() { | |
| 435 } | |
| 436 | |
| 437 int VariableRowHeightScrollHelper::GetPageScrollIncrement( | |
| 438 ScrollView* scroll_view, bool is_horizontal, bool is_positive) { | |
| 439 if (is_horizontal) | |
| 440 return 0; | |
| 441 // y coordinate is most likely negative. | |
| 442 int y = abs(scroll_view->GetContents()->y()); | |
| 443 int vis_height = scroll_view->GetContents()->parent()->height(); | |
| 444 if (is_positive) { | |
| 445 // Align the bottom most row to the top of the view. | |
| 446 int bottom = std::min(scroll_view->GetContents()->height() - 1, | |
| 447 y + vis_height); | |
| 448 RowInfo bottom_row_info = GetRowInfo(bottom); | |
| 449 // If 0, ScrollView will provide a default value. | |
| 450 return std::max(0, bottom_row_info.origin - y); | |
| 451 } else { | |
| 452 // Align the row on the previous page to to the top of the view. | |
| 453 int last_page_y = y - vis_height; | |
| 454 RowInfo last_page_info = GetRowInfo(std::max(0, last_page_y)); | |
| 455 if (last_page_y != last_page_info.origin) | |
| 456 return std::max(0, y - last_page_info.origin - last_page_info.height); | |
| 457 return std::max(0, y - last_page_info.origin); | |
| 458 } | |
| 459 } | |
| 460 | |
| 461 int VariableRowHeightScrollHelper::GetLineScrollIncrement( | |
| 462 ScrollView* scroll_view, bool is_horizontal, bool is_positive) { | |
| 463 if (is_horizontal) | |
| 464 return 0; | |
| 465 // y coordinate is most likely negative. | |
| 466 int y = abs(scroll_view->GetContents()->y()); | |
| 467 RowInfo row = GetRowInfo(y); | |
| 468 if (is_positive) { | |
| 469 return row.height - (y - row.origin); | |
| 470 } else if (y == row.origin) { | |
| 471 row = GetRowInfo(std::max(0, row.origin - 1)); | |
| 472 return y - row.origin; | |
| 473 } else { | |
| 474 return y - row.origin; | |
| 475 } | |
| 476 } | |
| 477 | |
| 478 VariableRowHeightScrollHelper::RowInfo | |
| 479 VariableRowHeightScrollHelper::GetRowInfo(int y) { | |
| 480 return controller_->GetRowInfo(y); | |
| 481 } | |
| 482 | |
| 483 // FixedRowHeightScrollHelper ----------------------------------------------- | |
| 484 | |
| 485 FixedRowHeightScrollHelper::FixedRowHeightScrollHelper(int top_margin, | |
| 486 int row_height) | |
| 487 : VariableRowHeightScrollHelper(NULL), | |
| 488 top_margin_(top_margin), | |
| 489 row_height_(row_height) { | |
| 490 DCHECK_GT(row_height, 0); | |
| 491 } | |
| 492 | |
| 493 VariableRowHeightScrollHelper::RowInfo | |
| 494 FixedRowHeightScrollHelper::GetRowInfo(int y) { | |
| 495 if (y < top_margin_) | |
| 496 return RowInfo(0, top_margin_); | |
| 497 return RowInfo((y - top_margin_) / row_height_ * row_height_ + top_margin_, | |
| 498 row_height_); | |
| 499 } | |
| 500 | |
| 501 } // namespace views | |
| OLD | NEW |