• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 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 "content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.h"
6 
7 #include "base/logging.h"
8 #include "content/common/input/input_event.h"
9 #include "ui/events/latency_info.h"
10 #include "ui/gfx/point_f.h"
11 
12 namespace content {
13 namespace {
14 
FloorTowardZero(const gfx::Vector2dF & vector)15 gfx::Vector2d FloorTowardZero(const gfx::Vector2dF& vector) {
16   int x = vector.x() > 0 ? floor(vector.x()) : ceil(vector.x());
17   int y = vector.y() > 0 ? floor(vector.y()) : ceil(vector.y());
18   return gfx::Vector2d(x, y);
19 }
20 
CeilFromZero(const gfx::Vector2dF & vector)21 gfx::Vector2d CeilFromZero(const gfx::Vector2dF& vector) {
22   int x = vector.x() > 0 ? ceil(vector.x()) : floor(vector.x());
23   int y = vector.y() > 0 ? ceil(vector.y()) : floor(vector.y());
24   return gfx::Vector2d(x, y);
25 }
26 
27 }  // namespace
28 
SyntheticSmoothScrollGesture(const SyntheticSmoothScrollGestureParams & params)29 SyntheticSmoothScrollGesture::SyntheticSmoothScrollGesture(
30     const SyntheticSmoothScrollGestureParams& params)
31     : params_(params),
32       gesture_source_type_(SyntheticGestureParams::DEFAULT_INPUT),
33       state_(SETUP) {}
34 
~SyntheticSmoothScrollGesture()35 SyntheticSmoothScrollGesture::~SyntheticSmoothScrollGesture() {}
36 
ForwardInputEvents(const base::TimeDelta & interval,SyntheticGestureTarget * target)37 SyntheticGesture::Result SyntheticSmoothScrollGesture::ForwardInputEvents(
38     const base::TimeDelta& interval, SyntheticGestureTarget* target) {
39   if (state_ == SETUP) {
40     gesture_source_type_ = params_.gesture_source_type;
41     if (gesture_source_type_ == SyntheticGestureParams::DEFAULT_INPUT)
42       gesture_source_type_ = target->GetDefaultSyntheticGestureSourceType();
43 
44     if (!target->SupportsSyntheticGestureSourceType(gesture_source_type_))
45       return SyntheticGesture::GESTURE_SOURCE_TYPE_NOT_SUPPORTED_BY_PLATFORM;
46 
47     state_ = STARTED;
48   }
49 
50   DCHECK_NE(gesture_source_type_, SyntheticGestureParams::DEFAULT_INPUT);
51   if (gesture_source_type_ == SyntheticGestureParams::TOUCH_INPUT)
52     ForwardTouchInputEvents(interval, target);
53   else if (gesture_source_type_ == SyntheticGestureParams::MOUSE_INPUT)
54     ForwardMouseInputEvents(interval, target);
55   else
56     return SyntheticGesture::GESTURE_SOURCE_TYPE_NOT_IMPLEMENTED;
57 
58   return (state_ == DONE) ? SyntheticGesture::GESTURE_FINISHED
59                           : SyntheticGesture::GESTURE_RUNNING;
60 }
61 
ForwardTouchInputEvents(const base::TimeDelta & interval,SyntheticGestureTarget * target)62 void SyntheticSmoothScrollGesture::ForwardTouchInputEvents(
63     const base::TimeDelta& interval, SyntheticGestureTarget* target) {
64   switch (state_) {
65     case STARTED:
66       // Check for an early finish.
67       if (params_.distance.IsZero()) {
68         state_ = DONE;
69         break;
70       }
71       AddTouchSlopToDistance(target);
72       PressTouchPoint(target);
73       state_ = MOVING;
74       break;
75     case MOVING:
76       total_delta_ += GetPositionDelta(interval);
77       MoveTouchPoint(target);
78 
79       if (HasScrolledEntireDistance()) {
80         if (params_.prevent_fling) {
81           state_ = STOPPING;
82         } else {
83           ReleaseTouchPoint(target);
84           state_ = DONE;
85         }
86       }
87       break;
88     case STOPPING:
89       total_stopping_wait_time_ += interval;
90       if (total_stopping_wait_time_ >= target->PointerAssumedStoppedTime()) {
91         // Send one last move event, but don't change the location. Without this
92         // we'd still sometimes cause a fling on Android.
93         MoveTouchPoint(target);
94         ReleaseTouchPoint(target);
95         state_ = DONE;
96       }
97       break;
98     case SETUP:
99       NOTREACHED()
100           << "State STARTED invalid for synthetic scroll using touch input.";
101     case DONE:
102       NOTREACHED()
103           << "State DONE invalid for synthetic scroll using touch input.";
104   }
105 }
106 
ForwardMouseInputEvents(const base::TimeDelta & interval,SyntheticGestureTarget * target)107 void SyntheticSmoothScrollGesture::ForwardMouseInputEvents(
108     const base::TimeDelta& interval, SyntheticGestureTarget* target) {
109   switch (state_) {
110     case STARTED:
111       // Check for an early finish.
112       if (params_.distance.IsZero()) {
113         state_ = DONE;
114         break;
115       }
116       state_ = MOVING;
117       // Fall through to forward the first event.
118     case MOVING:
119       {
120         // Even though WebMouseWheelEvents take floating point deltas,
121         // internally the scroll position is stored as an integer. We therefore
122         // keep track of the discrete delta which is consistent with the
123         // internal scrolling state. This ensures that when the gesture has
124         // finished we've scrolled exactly the specified distance.
125         total_delta_ += GetPositionDelta(interval);
126         gfx::Vector2d delta_discrete =
127             FloorTowardZero(total_delta_ - total_delta_discrete_);
128         ForwardMouseWheelEvent(target, delta_discrete);
129         total_delta_discrete_ += delta_discrete;
130       }
131       if (HasScrolledEntireDistance())
132         state_ = DONE;
133       break;
134     case SETUP:
135       NOTREACHED()
136           << "State STARTED invalid for synthetic scroll using touch input.";
137     case STOPPING:
138       NOTREACHED()
139           << "State STOPPING invalid for synthetic scroll using touch input.";
140     case DONE:
141       NOTREACHED()
142           << "State DONE invalid for synthetic scroll using touch input.";
143     }
144 }
145 
ForwardTouchEvent(SyntheticGestureTarget * target) const146 void SyntheticSmoothScrollGesture::ForwardTouchEvent(
147     SyntheticGestureTarget* target) const {
148   target->DispatchInputEventToPlatform(
149       InputEvent(touch_event_, ui::LatencyInfo(), false));
150 }
151 
ForwardMouseWheelEvent(SyntheticGestureTarget * target,const gfx::Vector2dF & delta) const152 void SyntheticSmoothScrollGesture::ForwardMouseWheelEvent(
153     SyntheticGestureTarget* target, const gfx::Vector2dF& delta) const {
154   blink::WebMouseWheelEvent mouse_wheel_event =
155       SyntheticWebMouseWheelEventBuilder::Build(delta.x(), delta.y(), 0, false);
156 
157   mouse_wheel_event.x = params_.anchor.x();
158   mouse_wheel_event.y = params_.anchor.y();
159 
160   target->DispatchInputEventToPlatform(
161       InputEvent(mouse_wheel_event, ui::LatencyInfo(), false));
162 }
163 
PressTouchPoint(SyntheticGestureTarget * target)164 void SyntheticSmoothScrollGesture::PressTouchPoint(
165     SyntheticGestureTarget* target) {
166   touch_event_.PressPoint(params_.anchor.x(), params_.anchor.y());
167   ForwardTouchEvent(target);
168 }
169 
MoveTouchPoint(SyntheticGestureTarget * target)170 void SyntheticSmoothScrollGesture::MoveTouchPoint(
171     SyntheticGestureTarget* target) {
172   gfx::PointF touch_position = params_.anchor + total_delta_;
173   touch_event_.MovePoint(0, touch_position.x(), touch_position.y());
174   ForwardTouchEvent(target);
175 }
176 
ReleaseTouchPoint(SyntheticGestureTarget * target)177 void SyntheticSmoothScrollGesture::ReleaseTouchPoint(
178     SyntheticGestureTarget* target) {
179   touch_event_.ReleasePoint(0);
180   ForwardTouchEvent(target);
181 }
182 
AddTouchSlopToDistance(SyntheticGestureTarget * target)183 void SyntheticSmoothScrollGesture::AddTouchSlopToDistance(
184     SyntheticGestureTarget* target) {
185   // Android uses euclidean distance to compute if a touch pointer has moved
186   // beyond the slop, while Aura uses Manhattan distance. We're using Euclidean
187   // distance and round up to the nearest integer.
188   // For vertical and horizontal scrolls (the common case), both methods produce
189   // the same result.
190   gfx::Vector2dF touch_slop_delta = ProjectLengthOntoScrollDirection(
191       target->GetTouchSlopInDips());
192   params_.distance += CeilFromZero(touch_slop_delta);
193 }
194 
GetPositionDelta(const base::TimeDelta & interval) const195 gfx::Vector2dF SyntheticSmoothScrollGesture::GetPositionDelta(
196     const base::TimeDelta& interval) const {
197   float delta_length = params_.speed_in_pixels_s * interval.InSecondsF();
198 
199   // Make sure we're not scrolling too far.
200   gfx::Vector2dF remaining_delta = ComputeRemainingDelta();
201   if (delta_length > remaining_delta.Length())
202     // In order to scroll in a certain direction we need to move the
203     // touch pointer/mouse wheel in the opposite direction.
204     return -remaining_delta;
205   else
206     return -ProjectLengthOntoScrollDirection(delta_length);
207 }
208 
ProjectLengthOntoScrollDirection(float delta_length) const209 gfx::Vector2dF SyntheticSmoothScrollGesture::ProjectLengthOntoScrollDirection(
210     float delta_length) const {
211   const float kTotalLength = params_.distance.Length();
212   return ScaleVector2d(params_.distance, delta_length / kTotalLength);
213 }
214 
ComputeRemainingDelta() const215 gfx::Vector2dF SyntheticSmoothScrollGesture::ComputeRemainingDelta() const {
216   return params_.distance + total_delta_;
217 }
218 
HasScrolledEntireDistance() const219 bool SyntheticSmoothScrollGesture::HasScrolledEntireDistance() const {
220   return ComputeRemainingDelta().IsZero();
221 }
222 
223 }  // namespace content
224