| // 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 |