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 } else if (params_.prevent_fling) {
92 ReleaseTouchPoint(target, event_timestamp);
98 if (timestamp - current_scroll_segment_stop_time_ >=
99 target->PointerAssumedStoppedTime()) {
100 event_timestamp = current_scroll_segment_stop_time_ +
101 target->PointerAssumedStoppedTime();
102 // Send one last move event, but don't change the location. Without this
103 // we'd still sometimes cause a fling on Android.
105 // Required to suppress flings on Aura, see
106 // |UpdateWebTouchPointFromUIEvent|, remove when crbug.com/332418
108 touch_event_.touches[0].position.y += 0.001f;
110 ForwardTouchEvent(target, event_timestamp);
111 ReleaseTouchPoint(target, event_timestamp);
117 << "State STARTED invalid for synthetic scroll using touch input.";
120 << "State DONE invalid for synthetic scroll using touch input.";
124 void SyntheticSmoothScrollGesture::ForwardMouseInputEvents(
125 const base::TimeTicks& timestamp, SyntheticGestureTarget* target) {
128 if (ScrollIsNoOp()) {
132 ComputeNextScrollSegment();
134 // Fall through to forward the first event.
136 // Even though WebMouseWheelEvents take floating point deltas,
137 // internally the scroll position is stored as an integer. We therefore
138 // keep track of the discrete delta which is consistent with the
139 // internal scrolling state. This ensures that when the gesture has
140 // finished we've scrolled exactly the specified distance.
141 base::TimeTicks event_timestamp = ClampTimestamp(timestamp);
142 gfx::Vector2dF current_scroll_segment_total_delta =
143 GetPositionDeltaAtTime(event_timestamp);
144 gfx::Vector2d delta_discrete =
145 FloorTowardZero(current_scroll_segment_total_delta -
146 current_scroll_segment_total_delta_discrete_);
147 ForwardMouseWheelEvent(target, delta_discrete, event_timestamp);
148 current_scroll_segment_total_delta_discrete_ += delta_discrete;
150 if (FinishedCurrentScrollSegment(event_timestamp)) {
151 if (!IsLastScrollSegment()) {
152 current_scroll_segment_total_delta_discrete_ = gfx::Vector2d();
153 ComputeNextScrollSegment();
154 ForwardMouseInputEvents(timestamp, target);
162 << "State STARTED invalid for synthetic scroll using touch input.";
165 << "State STOPPING invalid for synthetic scroll using touch input.";
168 << "State DONE invalid for synthetic scroll using touch input.";
172 void SyntheticSmoothScrollGesture::ForwardTouchEvent(
173 SyntheticGestureTarget* target, const base::TimeTicks& timestamp) {
174 touch_event_.timeStampSeconds = ConvertTimestampToSeconds(timestamp);
176 target->DispatchInputEventToPlatform(touch_event_);
179 void SyntheticSmoothScrollGesture::ForwardMouseWheelEvent(
180 SyntheticGestureTarget* target,
181 const gfx::Vector2dF& delta,
182 const base::TimeTicks& timestamp) const {
183 blink::WebMouseWheelEvent mouse_wheel_event =
184 SyntheticWebMouseWheelEventBuilder::Build(delta.x(), delta.y(), 0, false);
186 mouse_wheel_event.x = params_.anchor.x();
187 mouse_wheel_event.y = params_.anchor.y();
189 mouse_wheel_event.timeStampSeconds = ConvertTimestampToSeconds(timestamp);
191 target->DispatchInputEventToPlatform(mouse_wheel_event);
194 void SyntheticSmoothScrollGesture::PressTouchPoint(
195 SyntheticGestureTarget* target, const base::TimeTicks& timestamp) {
196 DCHECK_EQ(current_scroll_segment_, 0);
197 touch_event_.PressPoint(params_.anchor.x(), params_.anchor.y());
198 ForwardTouchEvent(target, timestamp);
201 void SyntheticSmoothScrollGesture::MoveTouchPoint(
202 SyntheticGestureTarget* target,
203 const gfx::Vector2dF& delta,
204 const base::TimeTicks& timestamp) {
205 DCHECK_GE(current_scroll_segment_, 0);
206 DCHECK_LT(current_scroll_segment_,
207 static_cast<int>(params_.distances.size()));
208 gfx::PointF touch_position = current_scroll_segment_start_position_ + delta;
209 touch_event_.MovePoint(0, touch_position.x(), touch_position.y());
210 ForwardTouchEvent(target, timestamp);
213 void SyntheticSmoothScrollGesture::ReleaseTouchPoint(
214 SyntheticGestureTarget* target, const base::TimeTicks& timestamp) {
215 DCHECK_EQ(current_scroll_segment_,
216 static_cast<int>(params_.distances.size()) - 1);
217 touch_event_.ReleasePoint(0);
218 ForwardTouchEvent(target, timestamp);
221 void SyntheticSmoothScrollGesture::AddTouchSlopToFirstDistance(
222 SyntheticGestureTarget* target) {
223 DCHECK_GE(params_.distances.size(), 1ul);
224 gfx::Vector2d& first_scroll_distance = params_.distances[0];
225 DCHECK_GT(first_scroll_distance.Length(), 0);
226 first_scroll_distance += CeilFromZero(ProjectScalarOntoVector(
227 target->GetTouchSlopInDips(), first_scroll_distance));
230 gfx::Vector2dF SyntheticSmoothScrollGesture::GetPositionDeltaAtTime(
231 const base::TimeTicks& timestamp) const {
232 // Make sure the final delta is correct. Using the computation below can lead
233 // to issues with floating point precision.
234 if (FinishedCurrentScrollSegment(timestamp))
235 return params_.distances[current_scroll_segment_];
238 params_.speed_in_pixels_s *
239 (timestamp - current_scroll_segment_start_time_).InSecondsF();
240 return ProjectScalarOntoVector(delta_length,
241 params_.distances[current_scroll_segment_]);
244 void SyntheticSmoothScrollGesture::ComputeNextScrollSegment() {
245 current_scroll_segment_++;
246 DCHECK_LT(current_scroll_segment_,
247 static_cast<int>(params_.distances.size()));
248 int64 total_duration_in_us = static_cast<int64>(
249 1e6 * (params_.distances[current_scroll_segment_].Length() /
250 params_.speed_in_pixels_s));
251 DCHECK_GT(total_duration_in_us, 0);
252 current_scroll_segment_start_time_ = current_scroll_segment_stop_time_;
253 current_scroll_segment_stop_time_ =
254 current_scroll_segment_start_time_ +
255 base::TimeDelta::FromMicroseconds(total_duration_in_us);
258 base::TimeTicks SyntheticSmoothScrollGesture::ClampTimestamp(
259 const base::TimeTicks& timestamp) const {
260 return std::min(timestamp, current_scroll_segment_stop_time_);
263 bool SyntheticSmoothScrollGesture::FinishedCurrentScrollSegment(
264 const base::TimeTicks& timestamp) const {
265 return timestamp >= current_scroll_segment_stop_time_;
268 bool SyntheticSmoothScrollGesture::IsLastScrollSegment() const {
269 DCHECK_LT(current_scroll_segment_,
270 static_cast<int>(params_.distances.size()));
271 return current_scroll_segment_ ==
272 static_cast<int>(params_.distances.size()) - 1;
275 bool SyntheticSmoothScrollGesture::ScrollIsNoOp() const {
276 return params_.distances.size() == 0 || params_.distances[0].IsZero();
279 } // namespace content