bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 1 | // Copyright 2015 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 "cc/layers/viewport.h" |
| 6 | |
| 7 | #include "base/logging.h" |
danakj | 60bc3bc | 2016-04-09 00:24:48 | [diff] [blame] | 8 | #include "base/memory/ptr_util.h" |
mdjones | 2ee41afd | 2016-10-27 16:50:20 | [diff] [blame] | 9 | #include "cc/input/browser_controls_offset_manager.h" |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 10 | #include "cc/trees/layer_tree_host_impl.h" |
| 11 | #include "cc/trees/layer_tree_impl.h" |
trchen | dba8b150 | 2016-07-08 09:47:01 | [diff] [blame] | 12 | #include "cc/trees/scroll_node.h" |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 13 | #include "ui/gfx/geometry/vector2d_conversions.h" |
| 14 | #include "ui/gfx/geometry/vector2d_f.h" |
| 15 | |
| 16 | namespace cc { |
| 17 | |
| 18 | // static |
danakj | 60bc3bc | 2016-04-09 00:24:48 | [diff] [blame] | 19 | std::unique_ptr<Viewport> Viewport::Create(LayerTreeHostImpl* host_impl) { |
| 20 | return base::WrapUnique(new Viewport(host_impl)); |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 21 | } |
| 22 | |
| 23 | Viewport::Viewport(LayerTreeHostImpl* host_impl) |
bokan | 499dd08 | 2015-06-24 15:35:19 | [diff] [blame] | 24 | : host_impl_(host_impl) |
| 25 | , pinch_zoom_active_(false) { |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 26 | DCHECK(host_impl_); |
| 27 | } |
| 28 | |
bokan | 9e556961 | 2015-06-09 18:41:20 | [diff] [blame] | 29 | void Viewport::Pan(const gfx::Vector2dF& delta) { |
| 30 | gfx::Vector2dF pending_delta = delta; |
bokan | 499dd08 | 2015-06-24 15:35:19 | [diff] [blame] | 31 | float page_scale = host_impl_->active_tree()->current_page_scale_factor(); |
| 32 | pending_delta.Scale(1 / page_scale); |
| 33 | InnerScrollLayer()->ScrollBy(pending_delta); |
bokan | 9e556961 | 2015-06-09 18:41:20 | [diff] [blame] | 34 | } |
| 35 | |
tdresser | 8fe1dd9 | 2015-07-09 17:21:07 | [diff] [blame] | 36 | Viewport::ScrollResult Viewport::ScrollBy(const gfx::Vector2dF& delta, |
| 37 | const gfx::Point& viewport_point, |
tdresser | 96f4a2b | 2015-07-15 14:11:54 | [diff] [blame] | 38 | bool is_direct_manipulation, |
bokan | 06a827b | 2016-11-14 22:53:42 | [diff] [blame] | 39 | bool affect_browser_controls, |
| 40 | bool scroll_outer_viewport) { |
bokan | 64008c4c | 2016-07-14 23:58:42 | [diff] [blame] | 41 | if (!OuterScrollLayer()) |
| 42 | return ScrollResult(); |
| 43 | |
tdresser | 8fe1dd9 | 2015-07-09 17:21:07 | [diff] [blame] | 44 | gfx::Vector2dF content_delta = delta; |
tdresser | 8fe1dd9 | 2015-07-09 17:21:07 | [diff] [blame] | 45 | |
bokan | 06a827b | 2016-11-14 22:53:42 | [diff] [blame] | 46 | if (affect_browser_controls && ShouldBrowserControlsConsumeScroll(delta)) |
mdjones | 2ee41afd | 2016-10-27 16:50:20 | [diff] [blame] | 47 | content_delta -= ScrollBrowserControls(delta); |
tdresser | 8fe1dd9 | 2015-07-09 17:21:07 | [diff] [blame] | 48 | |
| 49 | gfx::Vector2dF pending_content_delta = content_delta; |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 50 | |
sunxd | 2668bea6 | 2016-03-17 00:49:47 | [diff] [blame] | 51 | ScrollTree& scroll_tree = |
| 52 | host_impl_->active_tree()->property_trees()->scroll_tree; |
| 53 | ScrollNode* inner_node = |
| 54 | scroll_tree.Node(InnerScrollLayer()->scroll_tree_index()); |
| 55 | pending_content_delta -= host_impl_->ScrollSingleNode( |
| 56 | inner_node, pending_content_delta, viewport_point, is_direct_manipulation, |
| 57 | &scroll_tree); |
tdresser | 96f4a2b | 2015-07-15 14:11:54 | [diff] [blame] | 58 | |
| 59 | ScrollResult result; |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 60 | |
bokan | 06a827b | 2016-11-14 22:53:42 | [diff] [blame] | 61 | if (scroll_outer_viewport) { |
| 62 | ScrollNode* outer_node = |
| 63 | scroll_tree.Node(OuterScrollLayer()->scroll_tree_index()); |
| 64 | pending_content_delta -= host_impl_->ScrollSingleNode( |
| 65 | outer_node, pending_content_delta, viewport_point, |
| 66 | is_direct_manipulation, &scroll_tree); |
| 67 | } |
| 68 | |
bokan | 49c85a92 | 2015-10-07 21:08:03 | [diff] [blame] | 69 | result.consumed_delta = delta - AdjustOverscroll(pending_content_delta); |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 70 | |
tdresser | 96f4a2b | 2015-07-15 14:11:54 | [diff] [blame] | 71 | result.content_scrolled_delta = content_delta - pending_content_delta; |
tdresser | 8fe1dd9 | 2015-07-09 17:21:07 | [diff] [blame] | 72 | return result; |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 73 | } |
| 74 | |
bokan | e248130 | 2016-10-03 23:01:07 | [diff] [blame] | 75 | void Viewport::ScrollByInnerFirst(const gfx::Vector2dF& delta) { |
| 76 | LayerImpl* scroll_layer = InnerScrollLayer(); |
| 77 | |
| 78 | gfx::Vector2dF unused_delta = scroll_layer->ScrollBy(delta); |
| 79 | if (!unused_delta.IsZero() && OuterScrollLayer()) |
| 80 | OuterScrollLayer()->ScrollBy(unused_delta); |
| 81 | } |
| 82 | |
ymalik | 99740e85 | 2016-04-07 04:18:13 | [diff] [blame] | 83 | bool Viewport::ShouldAnimateViewport(const gfx::Vector2dF& viewport_delta, |
| 84 | const gfx::Vector2dF& pending_delta) { |
| 85 | float max_dim_viewport_delta = |
| 86 | std::max(std::abs(viewport_delta.x()), std::abs(viewport_delta.y())); |
| 87 | float max_dim_pending_delta = |
| 88 | std::max(std::abs(pending_delta.x()), std::abs(pending_delta.y())); |
| 89 | return max_dim_viewport_delta > max_dim_pending_delta; |
| 90 | } |
| 91 | |
ymalik | ddfc352 | 2016-09-05 18:40:37 | [diff] [blame] | 92 | gfx::Vector2dF Viewport::ScrollAnimated(const gfx::Vector2dF& delta, |
| 93 | base::TimeDelta delayed_by) { |
bokan | 64008c4c | 2016-07-14 23:58:42 | [diff] [blame] | 94 | if (!OuterScrollLayer()) |
| 95 | return gfx::Vector2dF(0, 0); |
| 96 | |
ymalik | 99740e85 | 2016-04-07 04:18:13 | [diff] [blame] | 97 | ScrollTree& scroll_tree = |
| 98 | host_impl_->active_tree()->property_trees()->scroll_tree; |
| 99 | |
| 100 | float scale_factor = host_impl_->active_tree()->current_page_scale_factor(); |
| 101 | gfx::Vector2dF scaled_delta = delta; |
| 102 | scaled_delta.Scale(1.f / scale_factor); |
| 103 | |
| 104 | ScrollNode* inner_node = |
| 105 | scroll_tree.Node(InnerScrollLayer()->scroll_tree_index()); |
| 106 | gfx::Vector2dF inner_delta = |
| 107 | host_impl_->ComputeScrollDelta(inner_node, delta); |
| 108 | |
| 109 | gfx::Vector2dF pending_delta = scaled_delta - inner_delta; |
| 110 | pending_delta.Scale(scale_factor); |
| 111 | |
| 112 | ScrollNode* outer_node = |
| 113 | scroll_tree.Node(OuterScrollLayer()->scroll_tree_index()); |
| 114 | gfx::Vector2dF outer_delta = |
| 115 | host_impl_->ComputeScrollDelta(outer_node, pending_delta); |
| 116 | |
| 117 | if (inner_delta.IsZero() && outer_delta.IsZero()) |
| 118 | return gfx::Vector2dF(0, 0); |
| 119 | |
| 120 | // Animate the viewport to which the majority of scroll delta will be applied. |
| 121 | // The animation system only supports running one scroll offset animation. |
| 122 | // TODO(ymalik): Fix the visible jump seen by instant scrolling one of the |
| 123 | // viewports. |
| 124 | bool will_animate = false; |
| 125 | if (ShouldAnimateViewport(inner_delta, outer_delta)) { |
| 126 | scroll_tree.ScrollBy(outer_node, outer_delta, host_impl_->active_tree()); |
ymalik | ddfc352 | 2016-09-05 18:40:37 | [diff] [blame] | 127 | will_animate = |
| 128 | host_impl_->ScrollAnimationCreate(inner_node, inner_delta, delayed_by); |
ymalik | 99740e85 | 2016-04-07 04:18:13 | [diff] [blame] | 129 | } else { |
| 130 | scroll_tree.ScrollBy(inner_node, inner_delta, host_impl_->active_tree()); |
ymalik | ddfc352 | 2016-09-05 18:40:37 | [diff] [blame] | 131 | will_animate = |
| 132 | host_impl_->ScrollAnimationCreate(outer_node, outer_delta, delayed_by); |
ymalik | 99740e85 | 2016-04-07 04:18:13 | [diff] [blame] | 133 | } |
| 134 | |
| 135 | if (will_animate) { |
| 136 | // Consume entire scroll delta as long as we are starting an animation. |
| 137 | return delta; |
| 138 | } |
| 139 | |
| 140 | pending_delta = scaled_delta - inner_delta - outer_delta; |
| 141 | pending_delta.Scale(scale_factor); |
| 142 | return pending_delta; |
| 143 | } |
| 144 | |
bokan | 499dd08 | 2015-06-24 15:35:19 | [diff] [blame] | 145 | void Viewport::SnapPinchAnchorIfWithinMargin(const gfx::Point& anchor) { |
danakj | ddaec91 | 2015-09-25 19:38:40 | [diff] [blame] | 146 | gfx::SizeF viewport_size = gfx::SizeF( |
| 147 | host_impl_->active_tree()->InnerViewportContainerLayer()->bounds()); |
bokan | 499dd08 | 2015-06-24 15:35:19 | [diff] [blame] | 148 | |
| 149 | if (anchor.x() < kPinchZoomSnapMarginDips) |
| 150 | pinch_anchor_adjustment_.set_x(-anchor.x()); |
| 151 | else if (anchor.x() > viewport_size.width() - kPinchZoomSnapMarginDips) |
| 152 | pinch_anchor_adjustment_.set_x(viewport_size.width() - anchor.x()); |
| 153 | |
| 154 | if (anchor.y() < kPinchZoomSnapMarginDips) |
| 155 | pinch_anchor_adjustment_.set_y(-anchor.y()); |
| 156 | else if (anchor.y() > viewport_size.height() - kPinchZoomSnapMarginDips) |
| 157 | pinch_anchor_adjustment_.set_y(viewport_size.height() - anchor.y()); |
| 158 | } |
| 159 | |
| 160 | void Viewport::PinchUpdate(float magnify_delta, const gfx::Point& anchor) { |
| 161 | if (!pinch_zoom_active_) { |
| 162 | // If this is the first pinch update and the pinch is within a margin- |
| 163 | // length of the screen edge, offset all updates by the amount so that we |
| 164 | // effectively snap the pinch zoom to the edge of the screen. This makes it |
| 165 | // easy to zoom in on position: fixed elements. |
bokan | 1f01388f | 2015-09-15 20:55:54 | [diff] [blame] | 166 | SnapPinchAnchorIfWithinMargin(anchor); |
bokan | 499dd08 | 2015-06-24 15:35:19 | [diff] [blame] | 167 | |
| 168 | pinch_zoom_active_ = true; |
| 169 | } |
| 170 | |
| 171 | LayerTreeImpl* active_tree = host_impl_->active_tree(); |
| 172 | |
| 173 | // Keep the center-of-pinch anchor specified by (x, y) in a stable |
| 174 | // position over the course of the magnify. |
| 175 | gfx::Point adjusted_anchor = anchor + pinch_anchor_adjustment_; |
| 176 | float page_scale = active_tree->current_page_scale_factor(); |
| 177 | gfx::PointF previous_scale_anchor = |
danakj | a2fdbc70 | 2015-10-20 23:05:24 | [diff] [blame] | 178 | gfx::ScalePoint(gfx::PointF(adjusted_anchor), 1.f / page_scale); |
bokan | 499dd08 | 2015-06-24 15:35:19 | [diff] [blame] | 179 | active_tree->SetPageScaleOnActiveTree(page_scale * magnify_delta); |
| 180 | page_scale = active_tree->current_page_scale_factor(); |
| 181 | gfx::PointF new_scale_anchor = |
danakj | a2fdbc70 | 2015-10-20 23:05:24 | [diff] [blame] | 182 | gfx::ScalePoint(gfx::PointF(adjusted_anchor), 1.f / page_scale); |
bokan | 499dd08 | 2015-06-24 15:35:19 | [diff] [blame] | 183 | gfx::Vector2dF move = previous_scale_anchor - new_scale_anchor; |
| 184 | |
| 185 | // Scale back to viewport space since that's the coordinate space ScrollBy |
| 186 | // uses. |
| 187 | move.Scale(page_scale); |
| 188 | |
| 189 | // If clamping the inner viewport scroll offset causes a change, it should |
| 190 | // be accounted for from the intended move. |
| 191 | move -= InnerScrollLayer()->ClampScrollToMaxScrollOffset(); |
| 192 | |
bokan | 1f01388f | 2015-09-15 20:55:54 | [diff] [blame] | 193 | Pan(move); |
bokan | 499dd08 | 2015-06-24 15:35:19 | [diff] [blame] | 194 | } |
| 195 | |
| 196 | void Viewport::PinchEnd() { |
| 197 | pinch_anchor_adjustment_ = gfx::Vector2d(); |
| 198 | pinch_zoom_active_ = false; |
| 199 | } |
| 200 | |
bokan | e248130 | 2016-10-03 23:01:07 | [diff] [blame] | 201 | LayerImpl* Viewport::MainScrollLayer() const { |
bokan | 5ded2662 | 2016-10-12 13:48:44 | [diff] [blame] | 202 | return OuterScrollLayer(); |
bokan | e248130 | 2016-10-03 23:01:07 | [diff] [blame] | 203 | } |
| 204 | |
mdjones | 2ee41afd | 2016-10-27 16:50:20 | [diff] [blame] | 205 | gfx::Vector2dF Viewport::ScrollBrowserControls(const gfx::Vector2dF& delta) { |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 206 | gfx::Vector2dF excess_delta = |
mdjones | 2ee41afd | 2016-10-27 16:50:20 | [diff] [blame] | 207 | host_impl_->browser_controls_manager()->ScrollBy(delta); |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 208 | |
| 209 | return delta - excess_delta; |
| 210 | } |
| 211 | |
mdjones | 2ee41afd | 2016-10-27 16:50:20 | [diff] [blame] | 212 | bool Viewport::ShouldBrowserControlsConsumeScroll( |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 213 | const gfx::Vector2dF& scroll_delta) const { |
mdjones | 2ee41afd | 2016-10-27 16:50:20 | [diff] [blame] | 214 | // Always consume if it's in the direction to show the browser controls. |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 215 | if (scroll_delta.y() < 0) |
| 216 | return true; |
| 217 | |
| 218 | if (TotalScrollOffset().y() < MaxTotalScrollOffset().y()) |
| 219 | return true; |
| 220 | |
| 221 | return false; |
| 222 | } |
| 223 | |
| 224 | gfx::Vector2dF Viewport::AdjustOverscroll(const gfx::Vector2dF& delta) const { |
tdresser | d9e20147 | 2015-07-31 13:15:07 | [diff] [blame] | 225 | // TODO(tdresser): Use a more rational epsilon. See crbug.com/510550 for |
| 226 | // details. |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 227 | const float kEpsilon = 0.1f; |
| 228 | gfx::Vector2dF adjusted = delta; |
| 229 | |
| 230 | if (std::abs(adjusted.x()) < kEpsilon) |
| 231 | adjusted.set_x(0.0f); |
| 232 | if (std::abs(adjusted.y()) < kEpsilon) |
| 233 | adjusted.set_y(0.0f); |
| 234 | |
bokan | aa274831 | 2015-03-26 00:10:37 | [diff] [blame] | 235 | return adjusted; |
| 236 | } |
| 237 | |
| 238 | gfx::ScrollOffset Viewport::MaxTotalScrollOffset() const { |
| 239 | gfx::ScrollOffset offset; |
| 240 | |
| 241 | offset += InnerScrollLayer()->MaxScrollOffset(); |
| 242 | |
| 243 | if (OuterScrollLayer()) |
| 244 | offset += OuterScrollLayer()->MaxScrollOffset(); |
| 245 | |
| 246 | return offset; |
| 247 | } |
| 248 | |
| 249 | gfx::ScrollOffset Viewport::TotalScrollOffset() const { |
| 250 | gfx::ScrollOffset offset; |
| 251 | |
| 252 | offset += InnerScrollLayer()->CurrentScrollOffset(); |
| 253 | |
| 254 | if (OuterScrollLayer()) |
| 255 | offset += OuterScrollLayer()->CurrentScrollOffset(); |
| 256 | |
| 257 | return offset; |
| 258 | } |
| 259 | |
| 260 | LayerImpl* Viewport::InnerScrollLayer() const { |
| 261 | return host_impl_->InnerViewportScrollLayer(); |
| 262 | } |
| 263 | |
| 264 | LayerImpl* Viewport::OuterScrollLayer() const { |
| 265 | return host_impl_->OuterViewportScrollLayer(); |
| 266 | } |
| 267 | |
| 268 | } // namespace cc |