blob: 7ccd58abff67b9823a9e8e7a540a539aed8665d8 [file] [log] [blame]
// Copyright 2017 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 "ui/events/blink/fling_booster.h"
using blink::WebGestureEvent;
using blink::WebInputEvent;
namespace {
// Minimum fling velocity required for the active fling and new fling for the
// two to accumulate.
const double kMinBoostFlingSpeedSquare = 350. * 350.;
// Minimum velocity for the active touch scroll to preserve (boost) an active
// fling for which cancellation has been deferred.
const double kMinBoostTouchScrollSpeedSquare = 150 * 150.;
// Timeout window after which the active fling will be cancelled if no animation
// ticks, scrolls or flings of sufficient velocity relative to the current fling
// are received. The default value on Android native views is 40ms, but we use a
// slightly increased value to accomodate small IPC message delays.
const double kFlingBoostTimeoutDelaySeconds = 0.05;
} // namespace
namespace ui {
FlingBooster::FlingBooster(const gfx::Vector2dF& fling_velocity,
blink::WebGestureDevice source_device,
int modifiers)
: current_fling_velocity_(fling_velocity),
source_device_(source_device),
modifiers_(modifiers),
deferred_fling_cancel_time_seconds_(0),
last_fling_animate_time_seconds_(0),
fling_boosted_(false) {}
bool FlingBooster::FilterGestureEventForFlingBoosting(
const WebGestureEvent& gesture_event,
bool* out_cancel_current_fling) {
DCHECK(out_cancel_current_fling);
*out_cancel_current_fling = false;
if (gesture_event.GetType() == WebInputEvent::kGestureFlingCancel) {
if (gesture_event.data.fling_cancel.prevent_boosting)
return false;
if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare)
return false;
deferred_fling_cancel_time_seconds_ =
gesture_event.TimeStamp().since_origin().InSecondsF() +
kFlingBoostTimeoutDelaySeconds;
return true;
}
// A fling is either inactive or is "free spinning", i.e., has yet to be
// interrupted by a touch gesture, in which case there is nothing to filter.
if (!deferred_fling_cancel_time_seconds_)
return false;
// Gestures from a different source should immediately interrupt the fling.
if (gesture_event.SourceDevice() != source_device_) {
*out_cancel_current_fling = true;
return false;
}
switch (gesture_event.GetType()) {
case WebInputEvent::kGestureTapCancel:
case WebInputEvent::kGestureTapDown:
return false;
case WebInputEvent::kGestureScrollBegin:
// TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to
// determine if the ScrollBegin should immediately cancel the fling.
ExtendBoostedFlingTimeout(gesture_event);
return true;
case WebInputEvent::kGestureScrollUpdate: {
if (gesture_event.data.scroll_update.inertial_phase ==
WebGestureEvent::kMomentumPhase) {
// GSU events in momentum phase are generated by FlingController to
// progress fling and should not interfere with fling boosting.
return false;
}
if (ShouldSuppressScrollForFlingBoosting(gesture_event)) {
ExtendBoostedFlingTimeout(gesture_event);
return true;
}
*out_cancel_current_fling = true;
return false;
}
case WebInputEvent::kGestureScrollEnd:
// Clear the last fling boost event *prior* to fling cancellation,
// preventing insertion of a synthetic GestureScrollBegin.
last_fling_boost_event_ = WebGestureEvent();
*out_cancel_current_fling = true;
return true;
case WebInputEvent::kGestureFlingStart: {
DCHECK_EQ(source_device_, gesture_event.SourceDevice());
gfx::Vector2dF new_fling_velocity(
gesture_event.data.fling_start.velocity_x,
gesture_event.data.fling_start.velocity_y);
DCHECK(!new_fling_velocity.IsZero());
fling_boosted_ = ShouldBoostFling(gesture_event);
if (fling_boosted_)
current_fling_velocity_ += new_fling_velocity;
else
current_fling_velocity_ = new_fling_velocity;
deferred_fling_cancel_time_seconds_ = 0;
last_fling_boost_event_ = WebGestureEvent();
return true;
}
default:
// All other types of gestures (taps, presses, etc...) will complete the
// deferred fling cancellation.
*out_cancel_current_fling = true;
return false;
}
}
bool FlingBooster::MustCancelDeferredFling() const {
return deferred_fling_cancel_time_seconds_ &&
last_fling_animate_time_seconds_ > deferred_fling_cancel_time_seconds_;
}
bool FlingBooster::ShouldBoostFling(const WebGestureEvent& fling_start_event) {
DCHECK_EQ(WebInputEvent::kGestureFlingStart, fling_start_event.GetType());
gfx::Vector2dF new_fling_velocity(
fling_start_event.data.fling_start.velocity_x,
fling_start_event.data.fling_start.velocity_y);
if (gfx::DotProduct(current_fling_velocity_, new_fling_velocity) <= 0)
return false;
if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare)
return false;
if (new_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare)
return false;
if (modifiers_ != fling_start_event.GetModifiers())
return false;
return true;
}
bool FlingBooster::ShouldSuppressScrollForFlingBoosting(
const WebGestureEvent& scroll_update_event) {
DCHECK_EQ(WebInputEvent::kGestureScrollUpdate, scroll_update_event.GetType());
gfx::Vector2dF dx(scroll_update_event.data.scroll_update.delta_x,
scroll_update_event.data.scroll_update.delta_y);
if (gfx::DotProduct(current_fling_velocity_, dx) <= 0)
return false;
const double time_since_last_fling_animate = std::max(
0.0, scroll_update_event.TimeStamp().since_origin().InSecondsF() -
last_fling_animate_time_seconds_);
if (time_since_last_fling_animate > kFlingBoostTimeoutDelaySeconds)
return false;
const double time_since_last_boost_event =
(scroll_update_event.TimeStamp() - last_fling_boost_event_.TimeStamp())
.InSecondsF();
if (time_since_last_boost_event < 0.001)
return true;
// TODO(jdduke): Use |scroll_update_event.data.scrollUpdate.velocity{X,Y}|.
// The scroll must be of sufficient velocity to maintain the active fling.
const gfx::Vector2dF scroll_velocity =
gfx::ScaleVector2d(dx, 1. / time_since_last_boost_event);
if (scroll_velocity.LengthSquared() < kMinBoostTouchScrollSpeedSquare)
return false;
return true;
}
void FlingBooster::ExtendBoostedFlingTimeout(
const blink::WebGestureEvent& event) {
deferred_fling_cancel_time_seconds_ =
event.TimeStamp().since_origin().InSecondsF() +
kFlingBoostTimeoutDelaySeconds;
last_fling_boost_event_ = event;
}
} // namespace ui