summaryrefslogtreecommitdiffstats
path: root/cc
diff options
context:
space:
mode:
authorbokan <bokan@chromium.org>2015-06-24 08:35:19 -0700
committerCommit bot <commit-bot@chromium.org>2015-06-24 15:35:47 +0000
commit499dd0841ce733af38260996a893e7f04dfcd450 (patch)
tree9a8b1c98b2aa2aaf8918d3c7d4c755d67f63a914 /cc
parentca2eecb014058605c7df8ba83b44a293bd8c3677 (diff)
downloadchromium_src-499dd0841ce733af38260996a893e7f04dfcd450.zip
chromium_src-499dd0841ce733af38260996a893e7f04dfcd450.tar.gz
chromium_src-499dd0841ce733af38260996a893e7f04dfcd450.tar.bz2
Snap pinch zoom gestures near the screen edge.
Pinch zooming in on elements near at the screen edges is difficult with pinch- zoom since the gesture will never be exactly on the edge. This is especially problematic when trying to pinch-zoom in on position:fixed elements. This patch adds a margin around the screen, currently 100px. If the first pinch- zoom gesture's center falls within the margin, we save the distance from the edge as the "anchor adjustment" and add it to all pinch updates for this gesture. The effect is that the pinch will behave as if the user created the gesture exactly over the edge, however, still allows movement away from the edge during the gesture. This patch also fixes the Viewport::Pan method not to use LTHI::ScrollLayerWithViewportSpaceDelta since it takes a screen-space delta and converts it to a local delta using the layer's transform. This transform is computed in CalcDrawProps and since we change the page_scale and immediately pan, the new scale isn't reflected in the transform. BUG=426138 CQ_INCLUDE_TRYBOTS=tryserver.blink:linux_blink_rel Review URL: https://codereview.chromium.org/1207573002 Cr-Commit-Position: refs/heads/master@{#335925}
Diffstat (limited to 'cc')
-rw-r--r--cc/layers/viewport.cc75
-rw-r--r--cc/layers/viewport.h16
-rw-r--r--cc/trees/layer_tree_host_impl.cc33
-rw-r--r--cc/trees/layer_tree_host_impl.h1
-rw-r--r--cc/trees/layer_tree_host_impl_unittest.cc160
5 files changed, 214 insertions, 71 deletions
diff --git a/cc/layers/viewport.cc b/cc/layers/viewport.cc
index 35db561..059e0a4 100644
--- a/cc/layers/viewport.cc
+++ b/cc/layers/viewport.cc
@@ -20,17 +20,16 @@ scoped_ptr<Viewport> Viewport::Create(
}
Viewport::Viewport(LayerTreeHostImpl* host_impl)
- : host_impl_(host_impl) {
+ : host_impl_(host_impl)
+ , pinch_zoom_active_(false) {
DCHECK(host_impl_);
}
void Viewport::Pan(const gfx::Vector2dF& delta) {
gfx::Vector2dF pending_delta = delta;
-
- pending_delta -= host_impl_->ScrollLayer(InnerScrollLayer(),
- pending_delta,
- gfx::Point(),
- false);
+ float page_scale = host_impl_->active_tree()->current_page_scale_factor();
+ pending_delta.Scale(1 / page_scale);
+ InnerScrollLayer()->ScrollBy(pending_delta);
}
Viewport::ScrollResult Viewport::ScrollBy(const gfx::Vector2dF& delta,
@@ -69,6 +68,70 @@ Viewport::ScrollResult Viewport::ScrollBy(const gfx::Vector2dF& delta,
return result;
}
+void Viewport::SnapPinchAnchorIfWithinMargin(const gfx::Point& anchor) {
+ gfx::SizeF viewport_size =
+ host_impl_->active_tree()->InnerViewportContainerLayer()->bounds();
+
+ if (anchor.x() < kPinchZoomSnapMarginDips)
+ pinch_anchor_adjustment_.set_x(-anchor.x());
+ else if (anchor.x() > viewport_size.width() - kPinchZoomSnapMarginDips)
+ pinch_anchor_adjustment_.set_x(viewport_size.width() - anchor.x());
+
+ if (anchor.y() < kPinchZoomSnapMarginDips)
+ pinch_anchor_adjustment_.set_y(-anchor.y());
+ else if (anchor.y() > viewport_size.height() - kPinchZoomSnapMarginDips)
+ pinch_anchor_adjustment_.set_y(viewport_size.height() - anchor.y());
+}
+
+void Viewport::PinchUpdate(float magnify_delta, const gfx::Point& anchor) {
+ if (!pinch_zoom_active_) {
+ // If this is the first pinch update and the pinch is within a margin-
+ // length of the screen edge, offset all updates by the amount so that we
+ // effectively snap the pinch zoom to the edge of the screen. This makes it
+ // easy to zoom in on position: fixed elements.
+ if (host_impl_->settings().invert_viewport_scroll_order)
+ SnapPinchAnchorIfWithinMargin(anchor);
+
+ pinch_zoom_active_ = true;
+ }
+
+ LayerTreeImpl* active_tree = host_impl_->active_tree();
+
+ // Keep the center-of-pinch anchor specified by (x, y) in a stable
+ // position over the course of the magnify.
+ gfx::Point adjusted_anchor = anchor + pinch_anchor_adjustment_;
+ float page_scale = active_tree->current_page_scale_factor();
+ gfx::PointF previous_scale_anchor =
+ gfx::ScalePoint(adjusted_anchor, 1.f / page_scale);
+ active_tree->SetPageScaleOnActiveTree(page_scale * magnify_delta);
+ page_scale = active_tree->current_page_scale_factor();
+ gfx::PointF new_scale_anchor =
+ gfx::ScalePoint(adjusted_anchor, 1.f / page_scale);
+ gfx::Vector2dF move = previous_scale_anchor - new_scale_anchor;
+
+ // Scale back to viewport space since that's the coordinate space ScrollBy
+ // uses.
+ move.Scale(page_scale);
+
+ // If clamping the inner viewport scroll offset causes a change, it should
+ // be accounted for from the intended move.
+ move -= InnerScrollLayer()->ClampScrollToMaxScrollOffset();
+
+ if (host_impl_->settings().invert_viewport_scroll_order) {
+ Pan(move);
+ } else {
+ gfx::Point viewport_point;
+ bool is_wheel_event = false;
+ bool affect_top_controls = false;
+ ScrollBy(move, viewport_point, is_wheel_event, affect_top_controls);
+ }
+}
+
+void Viewport::PinchEnd() {
+ pinch_anchor_adjustment_ = gfx::Vector2d();
+ pinch_zoom_active_ = false;
+}
+
gfx::Vector2dF Viewport::ScrollTopControls(const gfx::Vector2dF& delta) {
gfx::Vector2dF excess_delta =
host_impl_->top_controls_manager()->ScrollBy(delta);
diff --git a/cc/layers/viewport.h b/cc/layers/viewport.h
index 794df21..01885d7 100644
--- a/cc/layers/viewport.h
+++ b/cc/layers/viewport.h
@@ -20,6 +20,11 @@ class LayerTreeHostImpl;
// class.
class CC_EXPORT Viewport {
public:
+ // If the pinch zoom anchor on the first PinchUpdate is within this length
+ // of the screen edge, "snap" the zoom to that edge. Experimentally
+ // determined.
+ static const int kPinchZoomSnapMarginDips = 100;
+
struct ScrollResult {
gfx::Vector2dF applied_delta;
gfx::Vector2dF unused_scroll_delta;
@@ -39,6 +44,9 @@ class CC_EXPORT Viewport {
bool is_wheel_scroll,
bool affect_top_controls);
+ void PinchUpdate(float magnify_delta, const gfx::Point& anchor);
+ void PinchEnd();
+
private:
explicit Viewport(LayerTreeHostImpl* host_impl);
@@ -54,8 +62,16 @@ class CC_EXPORT Viewport {
LayerImpl* InnerScrollLayer() const;
LayerImpl* OuterScrollLayer() const;
+ void SnapPinchAnchorIfWithinMargin(const gfx::Point& anchor);
+
LayerTreeHostImpl* host_impl_;
+ bool pinch_zoom_active_;
+
+ // The pinch zoom anchor point is adjusted by this amount during a pinch. This
+ // is used to "snap" a pinch-zoom to the edge of the screen.
+ gfx::Vector2d pinch_anchor_adjustment_;
+
DISALLOW_COPY_AND_ASSIGN(Viewport);
};
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 42bd889..2344c03 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -2922,7 +2922,6 @@ bool LayerTreeHostImpl::HandleMouseOverScrollbar(LayerImpl* layer_impl,
void LayerTreeHostImpl::PinchGestureBegin() {
pinch_gesture_active_ = true;
- previous_pinch_anchor_ = gfx::Point();
client_->RenewTreePriority();
pinch_gesture_end_should_clear_scrolling_layer_ = !CurrentlyScrollingLayer();
if (active_tree_->OuterViewportScrollLayer()) {
@@ -2947,36 +2946,7 @@ void LayerTreeHostImpl::PinchGestureUpdate(float magnify_delta,
// the pinch update.
active_tree_->SetRootLayerScrollOffsetDelegate(NULL);
- // Keep the center-of-pinch anchor specified by (x, y) in a stable
- // position over the course of the magnify.
- float page_scale = active_tree_->current_page_scale_factor();
- gfx::PointF previous_scale_anchor = gfx::ScalePoint(anchor, 1.f / page_scale);
- active_tree_->SetPageScaleOnActiveTree(page_scale * magnify_delta);
- page_scale = active_tree_->current_page_scale_factor();
- gfx::PointF new_scale_anchor = gfx::ScalePoint(anchor, 1.f / page_scale);
- gfx::Vector2dF move = previous_scale_anchor - new_scale_anchor;
-
- // Scale back to viewport space since that's the coordinate space ScrollBy
- // uses.
- move.Scale(page_scale);
-
- previous_pinch_anchor_ = anchor;
-
- // If clamping the inner viewport scroll offset causes a change, it should
- // be accounted for from the intended move.
- move -= InnerViewportScrollLayer()->ClampScrollToMaxScrollOffset();
-
- if (settings().invert_viewport_scroll_order) {
- viewport()->Pan(move);
- } else {
- gfx::Point viewport_point;
- bool is_wheel_event = false;
- bool affect_top_controls = false;
- viewport()->ScrollBy(move,
- viewport_point,
- is_wheel_event,
- affect_top_controls);
- }
+ viewport()->PinchUpdate(magnify_delta, anchor);
active_tree_->SetRootLayerScrollOffsetDelegate(
root_layer_scroll_offset_delegate_);
@@ -2992,6 +2962,7 @@ void LayerTreeHostImpl::PinchGestureEnd() {
pinch_gesture_end_should_clear_scrolling_layer_ = false;
ClearCurrentlyScrollingLayer();
}
+ viewport()->PinchEnd();
top_controls_manager_->PinchEnd();
client_->SetNeedsCommitOnImplThread();
// When a pinch ends, we may be displaying content cached at incorrect scales,
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index fd5e7ec..592be94 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -709,7 +709,6 @@ class CC_EXPORT LayerTreeHostImpl
bool pinch_gesture_active_;
bool pinch_gesture_end_should_clear_scrolling_layer_;
- gfx::Point previous_pinch_anchor_;
scoped_ptr<TopControlsManager> top_controls_manager_;
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 6e05d38..f444fc3 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -28,6 +28,7 @@
#include "cc/layers/solid_color_scrollbar_layer_impl.h"
#include "cc/layers/texture_layer_impl.h"
#include "cc/layers/video_layer_impl.h"
+#include "cc/layers/viewport.h"
#include "cc/output/begin_frame_args.h"
#include "cc/output/compositor_frame_ack.h"
#include "cc/output/compositor_frame_metadata.h"
@@ -293,6 +294,34 @@ class LayerTreeHostImplTest : public testing::Test,
return scroll_layer;
}
+ // Sets up a typical virtual viewport setup with one child content layer.
+ // Returns a pointer to the content layer.
+ LayerImpl* CreateBasicVirtualViewportLayers(const gfx::Size& viewport_size,
+ const gfx::Size& content_size) {
+ // CreateScrollAndContentsLayers makes the outer viewport unscrollable and
+ // the inner a different size from the outer. We'll reuse its layer
+ // hierarchy but adjust the sizing to our needs.
+ CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
+
+ LayerImpl* content_layer =
+ host_impl_->OuterViewportScrollLayer()->children().back();
+ content_layer->SetBounds(content_size);
+ host_impl_->OuterViewportScrollLayer()->SetBounds(content_size);
+
+ LayerImpl* outer_clip = host_impl_->OuterViewportScrollLayer()->parent();
+ outer_clip->SetBounds(viewport_size);
+
+ LayerImpl* inner_clip_layer =
+ host_impl_->InnerViewportScrollLayer()->parent()->parent();
+ inner_clip_layer->SetBounds(viewport_size);
+ host_impl_->InnerViewportScrollLayer()->SetBounds(viewport_size);
+
+ host_impl_->SetViewportSize(viewport_size);
+ host_impl_->active_tree()->DidBecomeActive();
+
+ return content_layer;
+ }
+
// TODO(wjmaclean) Add clip-layer pointer to parameters.
scoped_ptr<LayerImpl> CreateScrollableLayer(int id,
const gfx::Size& size,
@@ -1090,30 +1119,21 @@ TEST_F(LayerTreeHostImplTest, ScrollDuringPinchScrollsInnerViewport) {
CreateHostImpl(settings,
CreateOutputSurface());
- LayerImpl* inner_scroll_layer =
- SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ const gfx::Size content_size(1000, 1000);
+ const gfx::Size viewport_size(500, 500);
+ CreateBasicVirtualViewportLayers(viewport_size, content_size);
- // Adjust the content layer to be larger than the outer viewport container so
- // that we get scrolling in both viewports.
- LayerImpl* content_layer =
- host_impl_->OuterViewportScrollLayer()->children().back();
LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
- LayerImpl* inner_clip_layer =
- host_impl_->InnerViewportScrollLayer()->parent()->parent();
- inner_clip_layer->SetBounds(gfx::Size(100, 100));
- outer_scroll_layer->SetBounds(gfx::Size(200, 200));
- content_layer->SetBounds(gfx::Size(200, 200));
-
- host_impl_->SetViewportSize(gfx::Size(100, 100));
+ LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
EXPECT_VECTOR_EQ(
- gfx::Vector2dF(100, 100),
+ gfx::Vector2dF(500, 500),
outer_scroll_layer->MaxScrollOffset());
- host_impl_->ScrollBegin(gfx::Point(99, 99), InputHandler::GESTURE);
+ host_impl_->ScrollBegin(gfx::Point(250, 250), InputHandler::GESTURE);
host_impl_->PinchGestureBegin();
- host_impl_->PinchGestureUpdate(2, gfx::Point(99, 99));
- host_impl_->ScrollBy(gfx::Point(99, 99), gfx::Vector2dF(10.f, 10.f));
+ host_impl_->PinchGestureUpdate(2, gfx::Point(250, 250));
+ host_impl_->ScrollBy(gfx::Point(250, 250), gfx::Vector2dF(10.f, 10.f));
host_impl_->PinchGestureEnd();
host_impl_->ScrollEnd();
@@ -1121,28 +1141,102 @@ TEST_F(LayerTreeHostImplTest, ScrollDuringPinchScrollsInnerViewport) {
gfx::Vector2dF(0, 0),
outer_scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(
- gfx::Vector2dF(50, 50),
+ gfx::Vector2dF(130, 130),
inner_scroll_layer->CurrentScrollOffset());
}
-TEST_F(LayerTreeHostImplTest, ImplPinchZoomWheelBubbleBetweenViewports) {
- LayerImpl* inner_scroll_layer =
- SetupScrollAndContentsLayers(gfx::Size(100, 100));
+// Tests the "snapping" of pinch-zoom gestures to the screen edge. That is, when
+// a pinch zoom is anchored within a certain margin of the screen edge, we
+// should assume the user means to scroll into the edge of the screen.
+TEST_F(LayerTreeHostImplTest, PinchZoomSnapsToScreenEdge) {
+ LayerTreeSettings settings = DefaultSettings();
+ settings.invert_viewport_scroll_order = true;
+ CreateHostImpl(settings,
+ CreateOutputSurface());
- // Adjust the content layer to be larger than the outer viewport container so
- // that we get scrolling in both viewports.
- LayerImpl* content_layer =
- host_impl_->OuterViewportScrollLayer()->children().back();
- LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
- LayerImpl* inner_clip_layer =
- host_impl_->InnerViewportScrollLayer()->parent()->parent();
- inner_clip_layer->SetBounds(gfx::Size(100, 100));
- outer_scroll_layer->SetBounds(gfx::Size(200, 200));
- content_layer->SetBounds(gfx::Size(200, 200));
+ const gfx::Size content_size(1000, 1000);
+ const gfx::Size viewport_size(500, 500);
+ CreateBasicVirtualViewportLayers(viewport_size, content_size);
- host_impl_->SetViewportSize(gfx::Size(100, 100));
+ int offsetFromEdge = Viewport::kPinchZoomSnapMarginDips - 5;
+ gfx::Point anchor(viewport_size.width() - offsetFromEdge,
+ viewport_size.height() - offsetFromEdge);
- DrawFrame();
+ // Pinch in within the margins. The scroll should stay exactly locked to the
+ // bottom and right.
+ host_impl_->ScrollBegin(anchor, InputHandler::GESTURE);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(2, anchor);
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+
+ EXPECT_VECTOR_EQ(
+ gfx::Vector2dF(250, 250),
+ host_impl_->InnerViewportScrollLayer()->CurrentScrollOffset());
+
+ // Reset.
+ host_impl_->active_tree()->SetPageScaleOnActiveTree(1.f);
+ host_impl_->InnerViewportScrollLayer()->SetScrollDelta(gfx::Vector2d());
+ host_impl_->OuterViewportScrollLayer()->SetScrollDelta(gfx::Vector2d());
+
+ // Pinch in within the margins. The scroll should stay exactly locked to the
+ // top and left.
+ anchor = gfx::Point(offsetFromEdge, offsetFromEdge);
+ host_impl_->ScrollBegin(anchor, InputHandler::GESTURE);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(2, anchor);
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+
+ EXPECT_VECTOR_EQ(
+ gfx::Vector2dF(0, 0),
+ host_impl_->InnerViewportScrollLayer()->CurrentScrollOffset());
+
+ // Reset.
+ host_impl_->active_tree()->SetPageScaleOnActiveTree(1.f);
+ host_impl_->InnerViewportScrollLayer()->SetScrollDelta(gfx::Vector2d());
+ host_impl_->OuterViewportScrollLayer()->SetScrollDelta(gfx::Vector2d());
+
+ // Pinch in just outside the margin. There should be no snapping.
+ offsetFromEdge = Viewport::kPinchZoomSnapMarginDips;
+ anchor = gfx::Point(offsetFromEdge, offsetFromEdge);
+ host_impl_->ScrollBegin(anchor, InputHandler::GESTURE);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(2, anchor);
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+
+ EXPECT_VECTOR_EQ(
+ gfx::Vector2dF(50, 50),
+ host_impl_->InnerViewportScrollLayer()->CurrentScrollOffset());
+
+ // Reset.
+ host_impl_->active_tree()->SetPageScaleOnActiveTree(1.f);
+ host_impl_->InnerViewportScrollLayer()->SetScrollDelta(gfx::Vector2d());
+ host_impl_->OuterViewportScrollLayer()->SetScrollDelta(gfx::Vector2d());
+
+ // Pinch in just outside the margin. There should be no snapping.
+ offsetFromEdge = Viewport::kPinchZoomSnapMarginDips;
+ anchor = gfx::Point(viewport_size.width() - offsetFromEdge,
+ viewport_size.height() - offsetFromEdge);
+ host_impl_->ScrollBegin(anchor, InputHandler::GESTURE);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(2, anchor);
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+
+ EXPECT_VECTOR_EQ(
+ gfx::Vector2dF(200, 200),
+ host_impl_->InnerViewportScrollLayer()->CurrentScrollOffset());
+}
+
+TEST_F(LayerTreeHostImplTest, ImplPinchZoomWheelBubbleBetweenViewports) {
+ const gfx::Size content_size(200, 200);
+ const gfx::Size viewport_size(100, 100);
+ CreateBasicVirtualViewportLayers(viewport_size, content_size);
+
+ LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
+ LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
// Zoom into the page by a 2X factor
float min_page_scale = 1.f, max_page_scale = 4.f;