blob: b2fb4bb7eb3bf82487025edf9b3e0b3e3d6bfda1 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.h"
#include "base/logging.h"
#include "content/common/input/input_event.h"
#include "ui/events/latency_info.h"
#include "ui/gfx/point_f.h"
namespace content {
namespace {
gfx::Vector2d FloorTowardZero(const gfx::Vector2dF& vector) {
int x = vector.x() > 0 ? floor(vector.x()) : ceil(vector.x());
int y = vector.y() > 0 ? floor(vector.y()) : ceil(vector.y());
return gfx::Vector2d(x, y);
}
gfx::Vector2d CeilFromZero(const gfx::Vector2dF& vector) {
int x = vector.x() > 0 ? ceil(vector.x()) : floor(vector.x());
int y = vector.y() > 0 ? ceil(vector.y()) : floor(vector.y());
return gfx::Vector2d(x, y);
}
} // namespace
SyntheticSmoothScrollGesture::SyntheticSmoothScrollGesture(
const SyntheticSmoothScrollGestureParams& params)
: params_(params),
gesture_source_type_(SyntheticGestureParams::DEFAULT_INPUT),
state_(SETUP) {}
SyntheticSmoothScrollGesture::~SyntheticSmoothScrollGesture() {}
SyntheticGesture::Result SyntheticSmoothScrollGesture::ForwardInputEvents(
const base::TimeDelta& interval, SyntheticGestureTarget* target) {
if (state_ == SETUP) {
gesture_source_type_ = params_.gesture_source_type;
if (gesture_source_type_ == SyntheticGestureParams::DEFAULT_INPUT)
gesture_source_type_ = target->GetDefaultSyntheticGestureSourceType();
if (!target->SupportsSyntheticGestureSourceType(gesture_source_type_))
return SyntheticGesture::GESTURE_SOURCE_TYPE_NOT_SUPPORTED_BY_PLATFORM;
state_ = STARTED;
}
DCHECK_NE(gesture_source_type_, SyntheticGestureParams::DEFAULT_INPUT);
if (gesture_source_type_ == SyntheticGestureParams::TOUCH_INPUT)
ForwardTouchInputEvents(interval, target);
else if (gesture_source_type_ == SyntheticGestureParams::MOUSE_INPUT)
ForwardMouseInputEvents(interval, target);
else
return SyntheticGesture::GESTURE_SOURCE_TYPE_NOT_IMPLEMENTED;
return (state_ == DONE) ? SyntheticGesture::GESTURE_FINISHED
: SyntheticGesture::GESTURE_RUNNING;
}
void SyntheticSmoothScrollGesture::ForwardTouchInputEvents(
const base::TimeDelta& interval, SyntheticGestureTarget* target) {
switch (state_) {
case STARTED:
// Check for an early finish.
if (params_.distance.IsZero()) {
state_ = DONE;
break;
}
AddTouchSlopToDistance(target);
PressTouchPoint(target);
state_ = MOVING;
break;
case MOVING:
total_delta_ += GetPositionDelta(interval);
MoveTouchPoint(target);
if (HasScrolledEntireDistance()) {
if (params_.prevent_fling) {
state_ = STOPPING;
} else {
ReleaseTouchPoint(target);
state_ = DONE;
}
}
break;
case STOPPING:
total_stopping_wait_time_ += interval;
if (total_stopping_wait_time_ >= target->PointerAssumedStoppedTime()) {
// Send one last move event, but don't change the location. Without this
// we'd still sometimes cause a fling on Android.
MoveTouchPoint(target);
ReleaseTouchPoint(target);
state_ = DONE;
}
break;
case SETUP:
NOTREACHED()
<< "State STARTED invalid for synthetic scroll using touch input.";
case DONE:
NOTREACHED()
<< "State DONE invalid for synthetic scroll using touch input.";
}
}
void SyntheticSmoothScrollGesture::ForwardMouseInputEvents(
const base::TimeDelta& interval, SyntheticGestureTarget* target) {
switch (state_) {
case STARTED:
// Check for an early finish.
if (params_.distance.IsZero()) {
state_ = DONE;
break;
}
state_ = MOVING;
// Fall through to forward the first event.
case MOVING:
{
// Even though WebMouseWheelEvents take floating point deltas,
// internally the scroll position is stored as an integer. We therefore
// keep track of the discrete delta which is consistent with the
// internal scrolling state. This ensures that when the gesture has
// finished we've scrolled exactly the specified distance.
total_delta_ += GetPositionDelta(interval);
gfx::Vector2d delta_discrete =
FloorTowardZero(total_delta_ - total_delta_discrete_);
ForwardMouseWheelEvent(target, delta_discrete);
total_delta_discrete_ += delta_discrete;
}
if (HasScrolledEntireDistance())
state_ = DONE;
break;
case SETUP:
NOTREACHED()
<< "State STARTED invalid for synthetic scroll using touch input.";
case STOPPING:
NOTREACHED()
<< "State STOPPING invalid for synthetic scroll using touch input.";
case DONE:
NOTREACHED()
<< "State DONE invalid for synthetic scroll using touch input.";
}
}
void SyntheticSmoothScrollGesture::ForwardTouchEvent(
SyntheticGestureTarget* target) const {
target->DispatchInputEventToPlatform(
InputEvent(touch_event_, ui::LatencyInfo(), false));
}
void SyntheticSmoothScrollGesture::ForwardMouseWheelEvent(
SyntheticGestureTarget* target, const gfx::Vector2dF& delta) const {
blink::WebMouseWheelEvent mouse_wheel_event =
SyntheticWebMouseWheelEventBuilder::Build(delta.x(), delta.y(), 0, false);
mouse_wheel_event.x = params_.anchor.x();
mouse_wheel_event.y = params_.anchor.y();
target->DispatchInputEventToPlatform(
InputEvent(mouse_wheel_event, ui::LatencyInfo(), false));
}
void SyntheticSmoothScrollGesture::PressTouchPoint(
SyntheticGestureTarget* target) {
touch_event_.PressPoint(params_.anchor.x(), params_.anchor.y());
ForwardTouchEvent(target);
}
void SyntheticSmoothScrollGesture::MoveTouchPoint(
SyntheticGestureTarget* target) {
gfx::PointF touch_position = params_.anchor + total_delta_;
touch_event_.MovePoint(0, touch_position.x(), touch_position.y());
ForwardTouchEvent(target);
}
void SyntheticSmoothScrollGesture::ReleaseTouchPoint(
SyntheticGestureTarget* target) {
touch_event_.ReleasePoint(0);
ForwardTouchEvent(target);
}
void SyntheticSmoothScrollGesture::AddTouchSlopToDistance(
SyntheticGestureTarget* target) {
// Android uses euclidean distance to compute if a touch pointer has moved
// beyond the slop, while Aura uses Manhattan distance. We're using Euclidean
// distance and round up to the nearest integer.
// For vertical and horizontal scrolls (the common case), both methods produce
// the same result.
gfx::Vector2dF touch_slop_delta = ProjectLengthOntoScrollDirection(
target->GetTouchSlopInDips());
params_.distance += CeilFromZero(touch_slop_delta);
}
gfx::Vector2dF SyntheticSmoothScrollGesture::GetPositionDelta(
const base::TimeDelta& interval) const {
float delta_length = params_.speed_in_pixels_s * interval.InSecondsF();
// Make sure we're not scrolling too far.
gfx::Vector2dF remaining_delta = ComputeRemainingDelta();
if (delta_length > remaining_delta.Length())
// In order to scroll in a certain direction we need to move the
// touch pointer/mouse wheel in the opposite direction.
return -remaining_delta;
else
return -ProjectLengthOntoScrollDirection(delta_length);
}
gfx::Vector2dF SyntheticSmoothScrollGesture::ProjectLengthOntoScrollDirection(
float delta_length) const {
const float kTotalLength = params_.distance.Length();
return ScaleVector2d(params_.distance, delta_length / kTotalLength);
}
gfx::Vector2dF SyntheticSmoothScrollGesture::ComputeRemainingDelta() const {
return params_.distance + total_delta_;
}
bool SyntheticSmoothScrollGesture::HasScrolledEntireDistance() const {
return ComputeRemainingDelta().IsZero();
}
} // namespace content