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