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.
5 #include "content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.h"
7 #include "base/logging.h"
8 #include "ui/gfx/point_f.h"
13 gfx::Vector2d
FloorTowardZero(const gfx::Vector2dF
& vector
) {
14 int x
= vector
.x() > 0 ? floor(vector
.x()) : ceil(vector
.x());
15 int y
= vector
.y() > 0 ? floor(vector
.y()) : ceil(vector
.y());
16 return gfx::Vector2d(x
, y
);
19 gfx::Vector2d
CeilFromZero(const gfx::Vector2dF
& vector
) {
20 int x
= vector
.x() > 0 ? ceil(vector
.x()) : floor(vector
.x());
21 int y
= vector
.y() > 0 ? ceil(vector
.y()) : floor(vector
.y());
22 return gfx::Vector2d(x
, y
);
25 gfx::Vector2dF
ProjectScalarOntoVector(
26 float scalar
, const gfx::Vector2d
& vector
) {
27 return gfx::ScaleVector2d(vector
, scalar
/ vector
.Length());
32 SyntheticSmoothScrollGesture::SyntheticSmoothScrollGesture(
33 const SyntheticSmoothScrollGestureParams
& params
)
35 gesture_source_type_(SyntheticGestureParams::DEFAULT_INPUT
),
38 SyntheticSmoothScrollGesture::~SyntheticSmoothScrollGesture() {}
40 SyntheticGesture::Result
SyntheticSmoothScrollGesture::ForwardInputEvents(
41 const base::TimeTicks
& timestamp
, SyntheticGestureTarget
* target
) {
42 if (state_
== SETUP
) {
43 gesture_source_type_
= params_
.gesture_source_type
;
44 if (gesture_source_type_
== SyntheticGestureParams::DEFAULT_INPUT
)
45 gesture_source_type_
= target
->GetDefaultSyntheticGestureSourceType();
48 current_scroll_segment_
= -1;
49 current_scroll_segment_stop_time_
= timestamp
;
52 DCHECK_NE(gesture_source_type_
, SyntheticGestureParams::DEFAULT_INPUT
);
53 if (gesture_source_type_
== SyntheticGestureParams::TOUCH_INPUT
)
54 ForwardTouchInputEvents(timestamp
, target
);
55 else if (gesture_source_type_
== SyntheticGestureParams::MOUSE_INPUT
)
56 ForwardMouseInputEvents(timestamp
, target
);
58 return SyntheticGesture::GESTURE_SOURCE_TYPE_NOT_IMPLEMENTED
;
60 return (state_
== DONE
) ? SyntheticGesture::GESTURE_FINISHED
61 : SyntheticGesture::GESTURE_RUNNING
;
64 void SyntheticSmoothScrollGesture::ForwardTouchInputEvents(
65 const base::TimeTicks
& timestamp
, SyntheticGestureTarget
* target
) {
66 base::TimeTicks event_timestamp
= timestamp
;
73 AddTouchSlopToFirstDistance(target
);
74 ComputeNextScrollSegment();
75 current_scroll_segment_start_position_
= params_
.anchor
;
76 PressTouchPoint(target
, event_timestamp
);
80 event_timestamp
= ClampTimestamp(timestamp
);
81 gfx::Vector2dF delta
= GetPositionDeltaAtTime(event_timestamp
);
82 MoveTouchPoint(target
, delta
, event_timestamp
);
84 if (FinishedCurrentScrollSegment(event_timestamp
)) {
85 if (!IsLastScrollSegment()) {
86 current_scroll_segment_start_position_
+=
87 params_
.distances
[current_scroll_segment_
];
88 ComputeNextScrollSegment();
89 // Start the next scroll immediately.
90 ForwardTouchInputEvents(timestamp
, target
);
91 } else if (params_
.prevent_fling
) {
94 ReleaseTouchPoint(target
, event_timestamp
);
100 if (timestamp
- current_scroll_segment_stop_time_
>=
101 target
->PointerAssumedStoppedTime()) {
102 event_timestamp
= current_scroll_segment_stop_time_
+
103 target
->PointerAssumedStoppedTime();
104 // Send one last move event, but don't change the location. Without this
105 // we'd still sometimes cause a fling on Android.
107 // Required to suppress flings on Aura, see
108 // |UpdateWebTouchPointFromUIEvent|, remove when crbug.com/332418
110 touch_event_
.touches
[0].position
.y
+= 0.001f
;
112 ForwardTouchEvent(target
, event_timestamp
);
113 ReleaseTouchPoint(target
, event_timestamp
);
119 << "State STARTED invalid for synthetic scroll using touch input.";
122 << "State DONE invalid for synthetic scroll using touch input.";
126 void SyntheticSmoothScrollGesture::ForwardMouseInputEvents(
127 const base::TimeTicks
& timestamp
, SyntheticGestureTarget
* target
) {
130 if (ScrollIsNoOp()) {
134 ComputeNextScrollSegment();
136 // Fall through to forward the first event.
138 // Even though WebMouseWheelEvents take floating point deltas,
139 // internally the scroll position is stored as an integer. We therefore
140 // keep track of the discrete delta which is consistent with the
141 // internal scrolling state. This ensures that when the gesture has
142 // finished we've scrolled exactly the specified distance.
143 base::TimeTicks event_timestamp
= ClampTimestamp(timestamp
);
144 gfx::Vector2dF current_scroll_segment_total_delta
=
145 GetPositionDeltaAtTime(event_timestamp
);
146 gfx::Vector2d delta_discrete
=
147 FloorTowardZero(current_scroll_segment_total_delta
-
148 current_scroll_segment_total_delta_discrete_
);
149 ForwardMouseWheelEvent(target
, delta_discrete
, event_timestamp
);
150 current_scroll_segment_total_delta_discrete_
+= delta_discrete
;
152 if (FinishedCurrentScrollSegment(event_timestamp
)) {
153 if (!IsLastScrollSegment()) {
154 current_scroll_segment_total_delta_discrete_
= gfx::Vector2d();
155 ComputeNextScrollSegment();
156 ForwardMouseInputEvents(timestamp
, target
);
164 << "State STARTED invalid for synthetic scroll using touch input.";
167 << "State STOPPING invalid for synthetic scroll using touch input.";
170 << "State DONE invalid for synthetic scroll using touch input.";
174 void SyntheticSmoothScrollGesture::ForwardTouchEvent(
175 SyntheticGestureTarget
* target
, const base::TimeTicks
& timestamp
) {
176 touch_event_
.timeStampSeconds
= ConvertTimestampToSeconds(timestamp
);
178 target
->DispatchInputEventToPlatform(touch_event_
);
181 void SyntheticSmoothScrollGesture::ForwardMouseWheelEvent(
182 SyntheticGestureTarget
* target
,
183 const gfx::Vector2dF
& delta
,
184 const base::TimeTicks
& timestamp
) const {
185 blink::WebMouseWheelEvent mouse_wheel_event
=
186 SyntheticWebMouseWheelEventBuilder::Build(delta
.x(), delta
.y(), 0, false);
188 mouse_wheel_event
.x
= params_
.anchor
.x();
189 mouse_wheel_event
.y
= params_
.anchor
.y();
191 mouse_wheel_event
.timeStampSeconds
= ConvertTimestampToSeconds(timestamp
);
193 target
->DispatchInputEventToPlatform(mouse_wheel_event
);
196 void SyntheticSmoothScrollGesture::PressTouchPoint(
197 SyntheticGestureTarget
* target
, const base::TimeTicks
& timestamp
) {
198 DCHECK_EQ(current_scroll_segment_
, 0);
199 touch_event_
.PressPoint(params_
.anchor
.x(), params_
.anchor
.y());
200 ForwardTouchEvent(target
, timestamp
);
203 void SyntheticSmoothScrollGesture::MoveTouchPoint(
204 SyntheticGestureTarget
* target
,
205 const gfx::Vector2dF
& delta
,
206 const base::TimeTicks
& timestamp
) {
207 DCHECK_GE(current_scroll_segment_
, 0);
208 DCHECK_LT(current_scroll_segment_
,
209 static_cast<int>(params_
.distances
.size()));
210 gfx::PointF touch_position
= current_scroll_segment_start_position_
+ delta
;
211 touch_event_
.MovePoint(0, touch_position
.x(), touch_position
.y());
212 ForwardTouchEvent(target
, timestamp
);
215 void SyntheticSmoothScrollGesture::ReleaseTouchPoint(
216 SyntheticGestureTarget
* target
, const base::TimeTicks
& timestamp
) {
217 DCHECK_EQ(current_scroll_segment_
,
218 static_cast<int>(params_
.distances
.size()) - 1);
219 touch_event_
.ReleasePoint(0);
220 ForwardTouchEvent(target
, timestamp
);
223 void SyntheticSmoothScrollGesture::AddTouchSlopToFirstDistance(
224 SyntheticGestureTarget
* target
) {
225 DCHECK_GE(params_
.distances
.size(), 1ul);
226 gfx::Vector2d
& first_scroll_distance
= params_
.distances
[0];
227 DCHECK_GT(first_scroll_distance
.Length(), 0);
228 first_scroll_distance
+= CeilFromZero(ProjectScalarOntoVector(
229 target
->GetTouchSlopInDips(), first_scroll_distance
));
232 gfx::Vector2dF
SyntheticSmoothScrollGesture::GetPositionDeltaAtTime(
233 const base::TimeTicks
& timestamp
) const {
234 // Make sure the final delta is correct. Using the computation below can lead
235 // to issues with floating point precision.
236 if (FinishedCurrentScrollSegment(timestamp
))
237 return params_
.distances
[current_scroll_segment_
];
240 params_
.speed_in_pixels_s
*
241 (timestamp
- current_scroll_segment_start_time_
).InSecondsF();
242 return ProjectScalarOntoVector(delta_length
,
243 params_
.distances
[current_scroll_segment_
]);
246 void SyntheticSmoothScrollGesture::ComputeNextScrollSegment() {
247 current_scroll_segment_
++;
248 DCHECK_LT(current_scroll_segment_
,
249 static_cast<int>(params_
.distances
.size()));
250 int64 total_duration_in_us
= static_cast<int64
>(
251 1e6
* (params_
.distances
[current_scroll_segment_
].Length() /
252 params_
.speed_in_pixels_s
));
253 DCHECK_GT(total_duration_in_us
, 0);
254 current_scroll_segment_start_time_
= current_scroll_segment_stop_time_
;
255 current_scroll_segment_stop_time_
=
256 current_scroll_segment_start_time_
+
257 base::TimeDelta::FromMicroseconds(total_duration_in_us
);
260 base::TimeTicks
SyntheticSmoothScrollGesture::ClampTimestamp(
261 const base::TimeTicks
& timestamp
) const {
262 return std::min(timestamp
, current_scroll_segment_stop_time_
);
265 bool SyntheticSmoothScrollGesture::FinishedCurrentScrollSegment(
266 const base::TimeTicks
& timestamp
) const {
267 return timestamp
>= current_scroll_segment_stop_time_
;
270 bool SyntheticSmoothScrollGesture::IsLastScrollSegment() const {
271 DCHECK_LT(current_scroll_segment_
,
272 static_cast<int>(params_
.distances
.size()));
273 return current_scroll_segment_
==
274 static_cast<int>(params_
.distances
.size()) - 1;
277 bool SyntheticSmoothScrollGesture::ScrollIsNoOp() const {
278 return params_
.distances
.size() == 0 || params_
.distances
[0].IsZero();
281 } // namespace content