Chromium Code Reviews| Index: ui/views/controls/scroll_view.cc |
| diff --git a/ui/views/controls/scroll_view.cc b/ui/views/controls/scroll_view.cc |
| index a3e1e9787a2ae32b3118c2d0ed9837d808f94d25..f75b629bfcb91eaf10a083ba9ef53d41a52349b6 100644 |
| --- a/ui/views/controls/scroll_view.cc |
| +++ b/ui/views/controls/scroll_view.cc |
| @@ -4,14 +4,17 @@ |
| #include "ui/views/controls/scroll_view.h" |
| +#include "base/feature_list.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| +#include "ui/compositor/overscroll/ui_scroll_input_manager.h" |
| #include "ui/events/event.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/native_theme/native_theme.h" |
| +#include "ui/views/background.h" |
| #include "ui/views/border.h" |
| #include "ui/views/style/platform_style.h" |
| -#include "ui/views/widget/root_view.h" |
| +#include "ui/views/widget/widget.h" |
| namespace views { |
| @@ -19,6 +22,15 @@ const char ScrollView::kViewClassName[] = "ScrollView"; |
| namespace { |
| +const base::Feature kToolkitViewsScrollWithLayers { |
| + "ToolkitViewsScrollWithLayers", |
| +#if defined(OS_MACOSX) |
| + base::FEATURE_ENABLED_BY_DEFAULT |
| +#else |
| + base::FEATURE_DISABLED_BY_DEFAULT |
| +#endif |
| +}; |
| + |
| // Subclass of ScrollView that resets the border when the theme changes. |
| class ScrollViewWithBorder : public views::ScrollView { |
| public: |
| @@ -64,15 +76,23 @@ int CheckScrollBounds(int viewport_size, int content_size, int current_pos) { |
| } |
| // Make sure the content is not scrolled out of bounds |
| -void CheckScrollBounds(View* viewport, View* view) { |
| +void CheckScrollBounds(View* viewport, View* container, View* view) { |
| if (!view) |
| return; |
| - int x = CheckScrollBounds(viewport->width(), view->width(), -view->x()); |
| - int y = CheckScrollBounds(viewport->height(), view->height(), -view->y()); |
| + gfx::ScrollOffset offset = container |
| + ? container->layer()->CurrentScrollOffset() |
| + : gfx::ScrollOffset(-view->x(), -view->y()); |
| + |
| + int x = CheckScrollBounds(viewport->width(), view->width(), offset.x()); |
| + int y = CheckScrollBounds(viewport->height(), view->height(), offset.y()); |
| - // This is no op if bounds are the same |
| - view->SetBounds(-x, -y, view->width(), view->height()); |
| + if (container) { |
| + container->layer()->SetScrollOffset(gfx::ScrollOffset(x, y)); |
| + } else { |
| + // This is no op if bounds are the same |
| + view->SetBounds(-x, -y, view->width(), view->height()); |
| + } |
| } |
| // Used by ScrollToPosition() to make sure the new position fits within the |
| @@ -89,6 +109,19 @@ int AdjustPosition(int current_position, |
| return (new_position > max_position) ? max_position : new_position; |
| } |
| +class ScrollViewContainer : public View { |
| + public: |
| + ScrollViewContainer() {} |
| + |
| + // View: |
| + void ChildPreferredSizeChanged(View* child) override { |
| + PreferredSizeChanged(); |
| + } |
| + |
| + private: |
| + DISALLOW_COPY_AND_ASSIGN(ScrollViewContainer); |
| +}; |
| + |
| } // namespace |
| // Viewport contains the contents View of the ScrollView. |
| @@ -105,9 +138,17 @@ class ScrollView::Viewport : public View { |
| View* contents = child_at(0); |
| gfx::Rect scroll_rect(rect); |
| + |
| + ScrollView* scroll_view = static_cast<ScrollView*>(parent()); |
| + if (contents == scroll_view->contents_container_) { |
| + // With layer scrolling, there's no need to "undo" the offset done in the |
| + // child's View::ScrollRectToVisible() before it calls this. |
| + DCHECK_EQ(0, contents->x()); |
| + DCHECK_EQ(0, contents->y()); |
| + } |
| + |
| scroll_rect.Offset(-contents->x(), -contents->y()); |
| - static_cast<ScrollView*>(parent())->ScrollContentsRegionToBeVisible( |
| - scroll_rect); |
| + scroll_view->ScrollContentsRegionToBeVisible(scroll_rect); |
| } |
| void ChildPreferredSizeChanged(View* child) override { |
| @@ -121,6 +162,7 @@ class ScrollView::Viewport : public View { |
| ScrollView::ScrollView() |
| : contents_(NULL), |
| + contents_container_(nullptr), |
| contents_viewport_(new Viewport()), |
| header_(NULL), |
| header_viewport_(new Viewport()), |
| @@ -142,6 +184,23 @@ ScrollView::ScrollView() |
| vert_sb_->SetVisible(false); |
| vert_sb_->set_controller(this); |
| corner_view_->SetVisible(false); |
| + |
| + if (!base::FeatureList::IsEnabled(kToolkitViewsScrollWithLayers)) |
| + return; |
| + |
| + contents_container_ = new ScrollViewContainer(); |
| + contents_viewport_->AddChildView(contents_container_); |
| + contents_viewport_->SetPaintToLayer(true); |
| + contents_viewport_->set_background( |
| + Background::CreateSolidBackground(SK_ColorWHITE)); |
| + contents_viewport_->layer()->SetMasksToBounds(true); |
| + |
| + contents_container_->SetPaintToLayer(true); |
|
sky
2016/08/02 17:29:45
Would it be possible to to turn on paint to layer
tapted
2016/08/03 00:18:58
One issue was that there's no guarantee that |cont
|
| + contents_container_->set_background( |
| + Background::CreateSolidBackground(SK_ColorWHITE)); |
| + contents_container_->layer()->SetScrollable( |
| + contents_viewport_->layer(), true /* can_overscroll */, |
| + base::Bind(&ScrollView::OnLayerScrolled, base::Unretained(this))); |
| } |
| ScrollView::~ScrollView() { |
| @@ -158,7 +217,8 @@ ScrollView* ScrollView::CreateScrollViewWithBorder() { |
| } |
| void ScrollView::SetContents(View* a_view) { |
| - SetHeaderOrContents(contents_viewport_, a_view, &contents_); |
| + View* parent = contents_container_ ? contents_container_ : contents_viewport_; |
| + SetHeaderOrContents(parent, a_view, &contents_); |
| } |
| void ScrollView::SetHeader(View* header) { |
| @@ -168,8 +228,9 @@ void ScrollView::SetHeader(View* header) { |
| gfx::Rect ScrollView::GetVisibleRect() const { |
| if (!contents_) |
| return gfx::Rect(); |
| - return gfx::Rect(-contents_->x(), -contents_->y(), |
| - contents_viewport_->width(), contents_viewport_->height()); |
| + gfx::ScrollOffset offset = CurrentOffset(); |
| + return gfx::Rect(offset.x(), offset.y(), contents_viewport_->width(), |
| + contents_viewport_->height()); |
| } |
| void ScrollView::ClipHeightTo(int min_height, int max_height) { |
| @@ -267,10 +328,15 @@ void ScrollView::Layout() { |
| // Update the bounds right now so the inner views can fit in it. |
| contents_viewport_->SetBoundsRect(viewport_bounds); |
| - // Give |contents_| a chance to update its bounds if it depends on the |
| - // viewport. |
| - if (contents_) |
| + // Give |contents_| a chance to update its bounds if it depends on its parent |
| + // bounds. |
| + if (contents_) { |
| + // The container bounds may need to be made larger later, but use the |
| + // viewport bounds to layout the contents. |
| + if (contents_container_) |
| + contents_container_->SetBoundsRect(viewport_bounds); |
| contents_->Layout(); |
| + } |
| bool should_layout_contents = false; |
| bool horiz_sb_required = false; |
| @@ -326,16 +392,29 @@ void ScrollView::Layout() { |
| // Update to the real client size with the visible scrollbars. |
| contents_viewport_->SetBoundsRect(viewport_bounds); |
| - if (should_layout_contents && contents_) |
| + if (should_layout_contents && contents_) { |
| + if (contents_container_) |
| + contents_container_->SetBoundsRect(viewport_bounds); |
| contents_->Layout(); |
| + } |
| + |
| + // Even when |contents_| needs to scroll, it can still be narrower or wider |
| + // the viewport. So ensure the scrolling layer can fill the viewport, so that |
| + // events will correctly hit it, and overscroll looks correct. |
| + if (contents_container_) { |
| + gfx::Size container_size = contents_ ? contents_->size() : gfx::Size(); |
| + container_size.SetToMax(viewport_bounds.size()); |
| + contents_container_->SetBoundsRect(gfx::Rect(container_size)); |
| + } |
| header_viewport_->SetBounds(contents_x, contents_y, |
| viewport_bounds.width(), header_height); |
| if (header_) |
| header_->Layout(); |
| - CheckScrollBounds(header_viewport_, header_); |
| - CheckScrollBounds(contents_viewport_, contents_); |
| + CheckScrollBounds(header_viewport_, nullptr, header_); |
| + CheckScrollBounds(contents_viewport_, contents_container_, contents_); |
| + |
| SchedulePaint(); |
| UpdateScrollBarPositions(); |
| } |
| @@ -379,6 +458,15 @@ void ScrollView::OnMouseExited(const ui::MouseEvent& event) { |
| vert_sb_->OnMouseExitedScrollView(event); |
| } |
| +void ScrollView::OnScrollEvent(ui::ScrollEvent* event) { |
| +#if defined(OS_MACOSX) |
| + ui::UIScrollInputManager* compositor_scroller = |
| + GetWidget()->GetCompositor()->scroll_input_manager(); |
| + if (compositor_scroller) |
| + compositor_scroller->OnScrollEvent(*event); |
| +#endif |
| +} |
| + |
| void ScrollView::OnGestureEvent(ui::GestureEvent* event) { |
| // If the event happened on one of the scrollbars, then those events are |
| // sent directly to the scrollbars. Otherwise, only scroll events are sent to |
| @@ -402,28 +490,32 @@ const char* ScrollView::GetClassName() const { |
| return kViewClassName; |
| } |
| +ScrollView* ScrollView::EnclosingScrollView() { |
| + return this; |
| +} |
| + |
| void ScrollView::ScrollToPosition(ScrollBar* source, int position) { |
| if (!contents_) |
| return; |
| + gfx::ScrollOffset offset = CurrentOffset(); |
| if (source == horiz_sb_ && horiz_sb_->visible()) { |
| - position = AdjustPosition(contents_->x(), position, contents_->width(), |
| + position = AdjustPosition(offset.x(), position, contents_->width(), |
| contents_viewport_->width()); |
| - if (-contents_->x() == position) |
| + if (offset.x() == position) |
| return; |
| - contents_->SetX(-position); |
| - if (header_) { |
| - header_->SetX(-position); |
| - header_->SchedulePaintInRect(header_->GetVisibleBounds()); |
| - } |
| + offset.set_x(position); |
| } else if (source == vert_sb_ && vert_sb_->visible()) { |
| - position = AdjustPosition(contents_->y(), position, contents_->height(), |
| + position = AdjustPosition(offset.y(), position, contents_->height(), |
| contents_viewport_->height()); |
| - if (-contents_->y() == position) |
| + if (offset.y() == position) |
| return; |
| - contents_->SetY(-position); |
| + offset.set_y(position); |
| } |
| - contents_->SchedulePaintInRect(contents_->GetVisibleBounds()); |
| + ScrollToOffset(offset); |
| + |
| + if (!contents_container_) |
| + contents_->SchedulePaintInRect(contents_->GetVisibleBounds()); |
| } |
| int ScrollView::GetScrollIncrement(ScrollBar* source, bool is_page, |
| @@ -504,10 +596,7 @@ void ScrollView::ScrollContentsRegionToBeVisible(const gfx::Rect& rect) { |
| (vis_rect.y() > y) ? y : std::max(0, max_y - |
| contents_viewport_->height()); |
| - contents_->SetX(-new_x); |
| - if (header_) |
| - header_->SetX(-new_x); |
| - contents_->SetY(-new_y); |
| + ScrollToOffset(gfx::ScrollOffset(new_x, new_y)); |
| UpdateScrollBarPositions(); |
| } |
| @@ -555,17 +644,52 @@ void ScrollView::UpdateScrollBarPositions() { |
| if (!contents_) |
| return; |
| + const gfx::ScrollOffset offset = CurrentOffset(); |
| if (horiz_sb_->visible()) { |
| int vw = contents_viewport_->width(); |
| int cw = contents_->width(); |
| - int origin = contents_->x(); |
| - horiz_sb_->Update(vw, cw, -origin); |
| + horiz_sb_->Update(vw, cw, offset.x()); |
| } |
| if (vert_sb_->visible()) { |
| int vh = contents_viewport_->height(); |
| int ch = contents_->height(); |
| - int origin = contents_->y(); |
| - vert_sb_->Update(vh, ch, -origin); |
| + vert_sb_->Update(vh, ch, offset.y()); |
| + } |
| +} |
| + |
| +gfx::ScrollOffset ScrollView::CurrentOffset() const { |
| + if (contents_container_) |
| + return contents_container_->layer()->CurrentScrollOffset(); |
| + return gfx::ScrollOffset(-contents_->x(), -contents_->y()); |
| +} |
| + |
| +void ScrollView::ScrollToOffset(const gfx::ScrollOffset& offset) { |
| + if (contents_container_) { |
| + contents_container_->layer()->SetScrollOffset(offset); |
| + |
| + // TODO(tapted): Remove this call to OnLayerScrolled(). It's unnecessary, |
| + // but will only be invoked (asynchronously) when a Compositor is present |
| + // and commits a frame, which isn't true in some tests. |
| + OnLayerScrolled(); |
| + } else { |
| + contents_->SetPosition(gfx::Point(-offset.x(), -offset.y())); |
| + ScrollHeader(); |
| + } |
| +} |
| + |
| +void ScrollView::OnLayerScrolled() { |
| + UpdateScrollBarPositions(); |
| + ScrollHeader(); |
| +} |
| + |
| +void ScrollView::ScrollHeader() { |
| + if (!header_) |
| + return; |
| + |
| + int x_offset = CurrentOffset().x(); |
| + if (header_->x() != -x_offset) { |
| + header_->SetX(-x_offset); |
| + header_->SchedulePaintInRect(header_->GetVisibleBounds()); |
| } |
| } |