| // Copyright 2014 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/gesture_detection/gesture_provider.h" |
| |
| #include <cmath> |
| |
| #include "base/auto_reset.h" |
| #include "base/trace_event/trace_event.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/gesture_detection/gesture_event_data.h" |
| #include "ui/events/gesture_detection/gesture_listeners.h" |
| #include "ui/events/gesture_detection/motion_event.h" |
| #include "ui/events/gesture_detection/motion_event_generic.h" |
| #include "ui/events/gesture_detection/scale_gesture_listeners.h" |
| #include "ui/gfx/geometry/point_f.h" |
| |
| namespace ui { |
| namespace { |
| |
| // Double-tap drag zoom sensitivity (speed). |
| const float kDoubleTapDragZoomSpeed = 0.005f; |
| |
| const char* GetMotionEventActionName(MotionEvent::Action action) { |
| switch (action) { |
| case MotionEvent::ACTION_POINTER_DOWN: |
| return "ACTION_POINTER_DOWN"; |
| case MotionEvent::ACTION_POINTER_UP: |
| return "ACTION_POINTER_UP"; |
| case MotionEvent::ACTION_DOWN: |
| return "ACTION_DOWN"; |
| case MotionEvent::ACTION_UP: |
| return "ACTION_UP"; |
| case MotionEvent::ACTION_CANCEL: |
| return "ACTION_CANCEL"; |
| case MotionEvent::ACTION_MOVE: |
| return "ACTION_MOVE"; |
| } |
| return ""; |
| } |
| |
| gfx::RectF ClampBoundingBox(const gfx::RectF& bounds, |
| float min_length, |
| float max_length) { |
| float width = bounds.width(); |
| float height = bounds.height(); |
| if (min_length) { |
| width = std::max(min_length, width); |
| height = std::max(min_length, height); |
| } |
| if (max_length) { |
| width = std::min(max_length, width); |
| height = std::min(max_length, height); |
| } |
| const gfx::PointF center = bounds.CenterPoint(); |
| return gfx::RectF( |
| center.x() - width / 2.f, center.y() - height / 2.f, width, height); |
| } |
| |
| } // namespace |
| |
| // GestureProvider:::Config |
| |
| GestureProvider::Config::Config() |
| : display(gfx::Display::kInvalidDisplayID, gfx::Rect(1, 1)), |
| disable_click_delay(false), |
| double_tap_support_for_platform_enabled(true), |
| gesture_begin_end_types_enabled(false), |
| min_gesture_bounds_length(0), |
| max_gesture_bounds_length(0) { |
| } |
| |
| GestureProvider::Config::~Config() { |
| } |
| |
| // GestureProvider::GestureListener |
| |
| class GestureProvider::GestureListenerImpl : public ScaleGestureListener, |
| public GestureListener, |
| public DoubleTapListener { |
| public: |
| GestureListenerImpl(const GestureProvider::Config& config, |
| GestureProviderClient* client) |
| : config_(config), |
| client_(client), |
| gesture_detector_(config.gesture_detector_config, this, this), |
| scale_gesture_detector_(config.scale_gesture_detector_config, this), |
| snap_scroll_controller_(config.gesture_detector_config.touch_slop, |
| config.display.size()), |
| ignore_multitouch_zoom_events_(false), |
| ignore_single_tap_(false), |
| pinch_event_sent_(false), |
| scroll_event_sent_(false), |
| max_diameter_before_show_press_(0), |
| show_press_event_sent_(false) {} |
| |
| void OnTouchEvent(const MotionEvent& event) { |
| const bool in_scale_gesture = IsScaleGestureDetectionInProgress(); |
| snap_scroll_controller_.SetSnapScrollMode(event, in_scale_gesture); |
| if (in_scale_gesture) |
| SetIgnoreSingleTap(true); |
| |
| const MotionEvent::Action action = event.GetAction(); |
| if (action == MotionEvent::ACTION_DOWN) { |
| current_down_time_ = event.GetEventTime(); |
| current_longpress_time_ = base::TimeTicks(); |
| ignore_single_tap_ = false; |
| scroll_event_sent_ = false; |
| pinch_event_sent_ = false; |
| show_press_event_sent_ = false; |
| gesture_detector_.set_longpress_enabled(true); |
| tap_down_point_ = gfx::PointF(event.GetX(), event.GetY()); |
| max_diameter_before_show_press_ = event.GetTouchMajor(); |
| } |
| |
| gesture_detector_.OnTouchEvent(event); |
| scale_gesture_detector_.OnTouchEvent(event); |
| |
| if (action == MotionEvent::ACTION_UP || |
| action == MotionEvent::ACTION_CANCEL) { |
| // Note: This call will have no effect if a fling was just generated, as |
| // |Fling()| will have already signalled an end to touch-scrolling. |
| if (scroll_event_sent_) |
| Send(CreateGesture(ET_GESTURE_SCROLL_END, event)); |
| current_down_time_ = base::TimeTicks(); |
| } else if (action == MotionEvent::ACTION_MOVE) { |
| if (!show_press_event_sent_ && !scroll_event_sent_) { |
| max_diameter_before_show_press_ = |
| std::max(max_diameter_before_show_press_, event.GetTouchMajor()); |
| } |
| } |
| } |
| |
| void Send(GestureEventData gesture) { |
| DCHECK(!gesture.time.is_null()); |
| // The only valid events that should be sent without an active touch |
| // sequence are SHOW_PRESS and TAP, potentially triggered by the double-tap |
| // delay timing out. |
| DCHECK(!current_down_time_.is_null() || gesture.type() == ET_GESTURE_TAP || |
| gesture.type() == ET_GESTURE_SHOW_PRESS || |
| gesture.type() == ET_GESTURE_BEGIN || |
| gesture.type() == ET_GESTURE_END); |
| |
| if (gesture.primary_tool_type == MotionEvent::TOOL_TYPE_UNKNOWN || |
| gesture.primary_tool_type == MotionEvent::TOOL_TYPE_FINGER) { |
| gesture.details.set_bounding_box( |
| ClampBoundingBox(gesture.details.bounding_box_f(), |
| config_.min_gesture_bounds_length, |
| config_.max_gesture_bounds_length)); |
| } |
| |
| switch (gesture.type()) { |
| case ET_GESTURE_LONG_PRESS: |
| DCHECK(!IsScaleGestureDetectionInProgress()); |
| current_longpress_time_ = gesture.time; |
| break; |
| case ET_GESTURE_LONG_TAP: |
| current_longpress_time_ = base::TimeTicks(); |
| break; |
| case ET_GESTURE_SCROLL_BEGIN: |
| DCHECK(!scroll_event_sent_); |
| scroll_event_sent_ = true; |
| break; |
| case ET_GESTURE_SCROLL_END: |
| DCHECK(scroll_event_sent_); |
| if (pinch_event_sent_) |
| Send(GestureEventData(ET_GESTURE_PINCH_END, gesture)); |
| scroll_event_sent_ = false; |
| break; |
| case ET_SCROLL_FLING_START: |
| DCHECK(scroll_event_sent_); |
| scroll_event_sent_ = false; |
| break; |
| case ET_GESTURE_PINCH_BEGIN: |
| DCHECK(!pinch_event_sent_); |
| if (!scroll_event_sent_) |
| Send(GestureEventData(ET_GESTURE_SCROLL_BEGIN, gesture)); |
| pinch_event_sent_ = true; |
| break; |
| case ET_GESTURE_PINCH_END: |
| DCHECK(pinch_event_sent_); |
| pinch_event_sent_ = false; |
| break; |
| case ET_GESTURE_SHOW_PRESS: |
| // It's possible that a double-tap drag zoom (from ScaleGestureDetector) |
| // will start before the press gesture fires (from GestureDetector), in |
| // which case the press should simply be dropped. |
| if (pinch_event_sent_ || scroll_event_sent_) |
| return; |
| default: |
| break; |
| }; |
| |
| client_->OnGestureEvent(gesture); |
| GestureTouchUMAHistogram::RecordGestureEvent(gesture); |
| } |
| |
| // ScaleGestureListener implementation. |
| bool OnScaleBegin(const ScaleGestureDetector& detector, |
| const MotionEvent& e) override { |
| if (ignore_multitouch_zoom_events_ && !detector.InDoubleTapMode()) |
| return false; |
| return true; |
| } |
| |
| void OnScaleEnd(const ScaleGestureDetector& detector, |
| const MotionEvent& e) override { |
| if (!pinch_event_sent_) |
| return; |
| Send(CreateGesture(ET_GESTURE_PINCH_END, e)); |
| } |
| |
| bool OnScale(const ScaleGestureDetector& detector, |
| const MotionEvent& e) override { |
| if (ignore_multitouch_zoom_events_ && !detector.InDoubleTapMode()) |
| return false; |
| if (!pinch_event_sent_) { |
| Send(CreateGesture(ET_GESTURE_PINCH_BEGIN, |
| e.GetPointerId(), |
| e.GetToolType(), |
| detector.GetEventTime(), |
| detector.GetFocusX(), |
| detector.GetFocusY(), |
| detector.GetFocusX() + e.GetRawOffsetX(), |
| detector.GetFocusY() + e.GetRawOffsetY(), |
| e.GetPointerCount(), |
| GetBoundingBox(e, ET_GESTURE_PINCH_BEGIN), |
| e.GetFlags())); |
| } |
| |
| if (std::abs(detector.GetCurrentSpan() - detector.GetPreviousSpan()) < |
| config_.scale_gesture_detector_config.min_pinch_update_span_delta) { |
| return false; |
| } |
| |
| float scale = detector.GetScaleFactor(); |
| if (scale == 1) |
| return true; |
| |
| if (detector.InDoubleTapMode()) { |
| // Relative changes in the double-tap scale factor computed by |detector| |
| // diminish as the touch moves away from the original double-tap focus. |
| // For historical reasons, Chrome has instead adopted a scale factor |
| // computation that is invariant to the focal distance, where |
| // the scale delta remains constant if the touch velocity is constant. |
| float dy = |
| (detector.GetCurrentSpanY() - detector.GetPreviousSpanY()) * 0.5f; |
| scale = std::pow(scale > 1 ? 1.0f + kDoubleTapDragZoomSpeed |
| : 1.0f - kDoubleTapDragZoomSpeed, |
| std::abs(dy)); |
| } |
| GestureEventDetails pinch_details(ET_GESTURE_PINCH_UPDATE); |
| pinch_details.set_scale(scale); |
| Send(CreateGesture(pinch_details, |
| e.GetPointerId(), |
| e.GetToolType(), |
| detector.GetEventTime(), |
| detector.GetFocusX(), |
| detector.GetFocusY(), |
| detector.GetFocusX() + e.GetRawOffsetX(), |
| detector.GetFocusY() + e.GetRawOffsetY(), |
| e.GetPointerCount(), |
| GetBoundingBox(e, pinch_details.type()), |
| e.GetFlags())); |
| return true; |
| } |
| |
| // GestureListener implementation. |
| bool OnDown(const MotionEvent& e) override { |
| GestureEventDetails tap_details(ET_GESTURE_TAP_DOWN); |
| Send(CreateGesture(tap_details, e)); |
| |
| // Return true to indicate that we want to handle touch. |
| return true; |
| } |
| |
| bool OnScroll(const MotionEvent& e1, |
| const MotionEvent& e2, |
| float raw_distance_x, |
| float raw_distance_y) override { |
| float distance_x = raw_distance_x; |
| float distance_y = raw_distance_y; |
| if (!scroll_event_sent_ && e2.GetPointerCount() == 1) { |
| // Remove the touch slop region from the first scroll event to |
| // avoid a jump. Touch slop isn't used for multi-finger |
| // gestures, so in those cases we don't subtract the slop. |
| float distance = |
| std::sqrt(distance_x * distance_x + distance_y * distance_y); |
| float epsilon = 1e-3f; |
| if (distance > epsilon) { |
| float ratio = |
| std::max(0.f, |
| distance - config_.gesture_detector_config.touch_slop) / |
| distance; |
| distance_x *= ratio; |
| distance_y *= ratio; |
| } |
| } |
| |
| snap_scroll_controller_.UpdateSnapScrollMode(distance_x, distance_y); |
| if (snap_scroll_controller_.IsSnappingScrolls()) { |
| if (snap_scroll_controller_.IsSnapHorizontal()) |
| distance_y = 0; |
| else |
| distance_x = 0; |
| } |
| |
| if (!distance_x && !distance_y) |
| return true; |
| |
| if (!scroll_event_sent_) { |
| // Note that scroll start hints are in distance traveled, where |
| // scroll deltas are in the opposite direction. |
| GestureEventDetails scroll_details( |
| ET_GESTURE_SCROLL_BEGIN, -raw_distance_x, -raw_distance_y); |
| |
| // Use the co-ordinates from the touch down, as these co-ordinates are |
| // used to determine which layer the scroll should affect. |
| Send(CreateGesture(scroll_details, e2.GetPointerId(), e2.GetToolType(), |
| e2.GetEventTime(), e1.GetX(), e1.GetY(), e1.GetRawX(), |
| e1.GetRawY(), e2.GetPointerCount(), |
| GetBoundingBox(e2, scroll_details.type()), |
| e2.GetFlags())); |
| DCHECK(scroll_event_sent_); |
| } |
| |
| GestureEventDetails scroll_details(ET_GESTURE_SCROLL_UPDATE, -distance_x, |
| -distance_y); |
| const gfx::RectF bounding_box = GetBoundingBox(e2, scroll_details.type()); |
| const gfx::PointF center = bounding_box.CenterPoint(); |
| const gfx::PointF raw_center = |
| center + gfx::Vector2dF(e2.GetRawOffsetX(), e2.GetRawOffsetY()); |
| Send(CreateGesture(scroll_details, e2.GetPointerId(), e2.GetToolType(), |
| e2.GetEventTime(), center.x(), center.y(), |
| raw_center.x(), raw_center.y(), e2.GetPointerCount(), |
| bounding_box, e2.GetFlags())); |
| |
| return true; |
| } |
| |
| bool OnFling(const MotionEvent& e1, |
| const MotionEvent& e2, |
| float velocity_x, |
| float velocity_y) override { |
| if (snap_scroll_controller_.IsSnappingScrolls()) { |
| if (snap_scroll_controller_.IsSnapHorizontal()) { |
| velocity_y = 0; |
| } else { |
| velocity_x = 0; |
| } |
| } |
| |
| if (!velocity_x && !velocity_y) |
| return true; |
| |
| if (!scroll_event_sent_) { |
| // The native side needs a ET_GESTURE_SCROLL_BEGIN before |
| // ET_SCROLL_FLING_START to send the fling to the correct target. |
| // The distance traveled in one second is a reasonable scroll start hint. |
| GestureEventDetails scroll_details( |
| ET_GESTURE_SCROLL_BEGIN, velocity_x, velocity_y); |
| Send(CreateGesture(scroll_details, e2)); |
| } |
| |
| GestureEventDetails fling_details( |
| ET_SCROLL_FLING_START, velocity_x, velocity_y); |
| Send(CreateGesture(fling_details, e2)); |
| return true; |
| } |
| |
| bool OnSwipe(const MotionEvent& e1, |
| const MotionEvent& e2, |
| float velocity_x, |
| float velocity_y) override { |
| GestureEventDetails swipe_details(ET_GESTURE_SWIPE, velocity_x, velocity_y); |
| Send(CreateGesture(swipe_details, e2)); |
| return true; |
| } |
| |
| bool OnTwoFingerTap(const MotionEvent& e1, const MotionEvent& e2) override { |
| // The location of the two finger tap event should be the location of the |
| // primary pointer. |
| GestureEventDetails two_finger_tap_details( |
| ET_GESTURE_TWO_FINGER_TAP, e1.GetTouchMajor(), e1.GetTouchMajor()); |
| Send(CreateGesture(two_finger_tap_details, |
| e2.GetPointerId(), |
| e2.GetToolType(), |
| e2.GetEventTime(), |
| e1.GetX(), |
| e1.GetY(), |
| e1.GetRawX(), |
| e1.GetRawY(), |
| e2.GetPointerCount(), |
| GetBoundingBox(e2, two_finger_tap_details.type()), |
| e2.GetFlags())); |
| return true; |
| } |
| |
| void OnShowPress(const MotionEvent& e) override { |
| GestureEventDetails show_press_details(ET_GESTURE_SHOW_PRESS); |
| show_press_event_sent_ = true; |
| Send(CreateGesture(show_press_details, e)); |
| } |
| |
| bool OnSingleTapUp(const MotionEvent& e) override { |
| // This is a hack to address the issue where user hovers |
| // over a link for longer than double_tap_timeout_, then |
| // OnSingleTapConfirmed() is not triggered. But we still |
| // want to trigger the tap event at UP. So we override |
| // OnSingleTapUp() in this case. This assumes singleTapUp |
| // gets always called before singleTapConfirmed. |
| if (!ignore_single_tap_) { |
| if (e.GetEventTime() - current_down_time_ > |
| config_.gesture_detector_config.double_tap_timeout) { |
| return OnSingleTapConfirmed(e); |
| } else if (!IsDoubleTapEnabled() || config_.disable_click_delay) { |
| // If double-tap has been disabled, there is no need to wait |
| // for the double-tap timeout. |
| return OnSingleTapConfirmed(e); |
| } else { |
| // Notify Blink about this tapUp event anyway, when none of the above |
| // conditions applied. |
| Send(CreateTapGesture(ET_GESTURE_TAP_UNCONFIRMED, e)); |
| } |
| } |
| |
| if (e.GetAction() == MotionEvent::ACTION_UP && |
| !current_longpress_time_.is_null() && |
| !IsScaleGestureDetectionInProgress()) { |
| GestureEventDetails long_tap_details(ET_GESTURE_LONG_TAP); |
| Send(CreateGesture(long_tap_details, e)); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // DoubleTapListener implementation. |
| bool OnSingleTapConfirmed(const MotionEvent& e) override { |
| // Long taps in the edges of the screen have their events delayed by |
| // ContentViewHolder for tab swipe operations. As a consequence of the delay |
| // this method might be called after receiving the up event. |
| // These corner cases should be ignored. |
| if (ignore_single_tap_) |
| return true; |
| |
| ignore_single_tap_ = true; |
| |
| Send(CreateTapGesture(ET_GESTURE_TAP, e)); |
| return true; |
| } |
| |
| bool OnDoubleTap(const MotionEvent& e) override { |
| return scale_gesture_detector_.OnDoubleTap(e); |
| } |
| |
| bool OnDoubleTapEvent(const MotionEvent& e) override { |
| switch (e.GetAction()) { |
| case MotionEvent::ACTION_DOWN: |
| gesture_detector_.set_longpress_enabled(false); |
| break; |
| |
| case MotionEvent::ACTION_UP: |
| if (!IsPinchInProgress() && !IsScrollInProgress()) { |
| Send(CreateTapGesture(ET_GESTURE_DOUBLE_TAP, e)); |
| return true; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| return false; |
| } |
| |
| void OnLongPress(const MotionEvent& e) override { |
| DCHECK(!IsDoubleTapInProgress()); |
| SetIgnoreSingleTap(true); |
| GestureEventDetails long_press_details(ET_GESTURE_LONG_PRESS); |
| Send(CreateGesture(long_press_details, e)); |
| } |
| |
| GestureEventData CreateGesture(const GestureEventDetails& details, |
| int motion_event_id, |
| MotionEvent::ToolType primary_tool_type, |
| base::TimeTicks time, |
| float x, |
| float y, |
| float raw_x, |
| float raw_y, |
| size_t touch_point_count, |
| const gfx::RectF& bounding_box, |
| int flags) { |
| return GestureEventData(details, |
| motion_event_id, |
| primary_tool_type, |
| time, |
| x, |
| y, |
| raw_x, |
| raw_y, |
| touch_point_count, |
| bounding_box, |
| flags); |
| } |
| |
| GestureEventData CreateGesture(EventType type, |
| int motion_event_id, |
| MotionEvent::ToolType primary_tool_type, |
| base::TimeTicks time, |
| float x, |
| float y, |
| float raw_x, |
| float raw_y, |
| size_t touch_point_count, |
| const gfx::RectF& bounding_box, |
| int flags) { |
| return GestureEventData(GestureEventDetails(type), |
| motion_event_id, |
| primary_tool_type, |
| time, |
| x, |
| y, |
| raw_x, |
| raw_y, |
| touch_point_count, |
| bounding_box, |
| flags); |
| } |
| |
| GestureEventData CreateGesture(const GestureEventDetails& details, |
| const MotionEvent& event) { |
| return GestureEventData(details, |
| event.GetPointerId(), |
| event.GetToolType(), |
| event.GetEventTime(), |
| event.GetX(), |
| event.GetY(), |
| event.GetRawX(), |
| event.GetRawY(), |
| event.GetPointerCount(), |
| GetBoundingBox(event, details.type()), |
| event.GetFlags()); |
| } |
| |
| GestureEventData CreateGesture(EventType type, const MotionEvent& event) { |
| return CreateGesture(GestureEventDetails(type), event); |
| } |
| |
| GestureEventData CreateTapGesture(EventType type, const MotionEvent& event) { |
| // Set the tap count to 1 even for ET_GESTURE_DOUBLE_TAP, in order to be |
| // consistent with double tap behavior on a mobile viewport. See |
| // crbug.com/234986 for context. |
| GestureEventDetails details(type); |
| details.set_tap_count(1); |
| return CreateGesture(details, event); |
| } |
| |
| gfx::RectF GetBoundingBox(const MotionEvent& event, EventType type) { |
| // Can't use gfx::RectF::Union, as it ignores touches with a radius of 0. |
| float left = std::numeric_limits<float>::max(); |
| float top = std::numeric_limits<float>::max(); |
| float right = -std::numeric_limits<float>::max(); |
| float bottom = -std::numeric_limits<float>::max(); |
| for (size_t i = 0; i < event.GetPointerCount(); ++i) { |
| float x, y, diameter; |
| // Only for the show press and tap events, the bounding box is calculated |
| // based on the touch start point and the maximum diameter before the |
| // show press event is sent. |
| if (type == ET_GESTURE_SHOW_PRESS || type == ET_GESTURE_TAP || |
| type == ET_GESTURE_TAP_UNCONFIRMED) { |
| DCHECK_EQ(0U, i); |
| diameter = max_diameter_before_show_press_; |
| x = tap_down_point_.x(); |
| y = tap_down_point_.y(); |
| } else { |
| diameter = event.GetTouchMajor(i); |
| x = event.GetX(i); |
| y = event.GetY(i); |
| } |
| x = x - diameter / 2; |
| y = y - diameter / 2; |
| left = std::min(left, x); |
| right = std::max(right, x + diameter); |
| top = std::min(top, y); |
| bottom = std::max(bottom, y + diameter); |
| } |
| return gfx::RectF(left, top, right - left, bottom - top); |
| } |
| |
| void SetDoubleTapEnabled(bool enabled) { |
| DCHECK(!IsDoubleTapInProgress()); |
| gesture_detector_.SetDoubleTapListener(enabled ? this : NULL); |
| } |
| |
| void SetMultiTouchZoomEnabled(bool enabled) { |
| // Note that returning false from |OnScaleBegin()| or |OnScale()| prevents |
| // the detector from emitting further scale updates for the current touch |
| // sequence. Thus, if multitouch events are enabled in the middle of a |
| // gesture, it will only take effect with the next gesture. |
| ignore_multitouch_zoom_events_ = !enabled; |
| } |
| |
| bool IsDoubleTapInProgress() const { |
| return gesture_detector_.is_double_tapping() || |
| (IsScaleGestureDetectionInProgress() && InDoubleTapMode()); |
| } |
| |
| bool IsScrollInProgress() const { return scroll_event_sent_; } |
| |
| bool IsPinchInProgress() const { return pinch_event_sent_; } |
| |
| private: |
| bool IsScaleGestureDetectionInProgress() const { |
| return scale_gesture_detector_.IsInProgress(); |
| } |
| |
| bool InDoubleTapMode() const { |
| return scale_gesture_detector_.InDoubleTapMode(); |
| } |
| |
| bool IsDoubleTapEnabled() const { |
| return gesture_detector_.has_doubletap_listener(); |
| } |
| |
| void SetIgnoreSingleTap(bool value) { ignore_single_tap_ = value; } |
| |
| const GestureProvider::Config config_; |
| GestureProviderClient* const client_; |
| |
| GestureDetector gesture_detector_; |
| ScaleGestureDetector scale_gesture_detector_; |
| SnapScrollController snap_scroll_controller_; |
| |
| base::TimeTicks current_down_time_; |
| |
| // Keeps track of the current GESTURE_LONG_PRESS event. If a context menu is |
| // opened after a GESTURE_LONG_PRESS, this is used to insert a |
| // GESTURE_TAP_CANCEL for removing any ::active styling. |
| base::TimeTicks current_longpress_time_; |
| |
| // Completely silence multi-touch (pinch) scaling events. Used in WebView when |
| // zoom support is turned off. |
| bool ignore_multitouch_zoom_events_; |
| |
| // TODO(klobag): This is to avoid a bug in GestureDetector. With multi-touch, |
| // always_in_tap_region_ is not reset. So when the last finger is up, |
| // |OnSingleTapUp()| will be mistakenly fired. |
| bool ignore_single_tap_; |
| |
| // Tracks whether {PINCH|SCROLL}_BEGIN events have been forwarded for the |
| // current touch sequence. |
| bool pinch_event_sent_; |
| bool scroll_event_sent_; |
| |
| // Only track the maximum diameter before the show press event has been |
| // sent and a tap must still be possible for this touch sequence. |
| float max_diameter_before_show_press_; |
| |
| gfx::PointF tap_down_point_; |
| |
| // Tracks whether an ET_GESTURE_SHOW_PRESS event has been sent for this touch |
| // sequence. |
| bool show_press_event_sent_; |
| |
| DISALLOW_COPY_AND_ASSIGN(GestureListenerImpl); |
| }; |
| |
| // GestureProvider |
| |
| GestureProvider::GestureProvider(const Config& config, |
| GestureProviderClient* client) |
| : double_tap_support_for_page_(true), |
| double_tap_support_for_platform_( |
| config.double_tap_support_for_platform_enabled), |
| gesture_begin_end_types_enabled_(config.gesture_begin_end_types_enabled) { |
| DCHECK(client); |
| DCHECK(!config.min_gesture_bounds_length || |
| !config.max_gesture_bounds_length || |
| config.min_gesture_bounds_length <= config.max_gesture_bounds_length); |
| TRACE_EVENT0("input", "GestureProvider::InitGestureDetectors"); |
| gesture_listener_.reset(new GestureListenerImpl(config, client)); |
| UpdateDoubleTapDetectionSupport(); |
| } |
| |
| GestureProvider::~GestureProvider() { |
| } |
| |
| bool GestureProvider::OnTouchEvent(const MotionEvent& event) { |
| TRACE_EVENT1("input", |
| "GestureProvider::OnTouchEvent", |
| "action", |
| GetMotionEventActionName(event.GetAction())); |
| |
| DCHECK_NE(0u, event.GetPointerCount()); |
| |
| if (!CanHandle(event)) |
| return false; |
| |
| OnTouchEventHandlingBegin(event); |
| gesture_listener_->OnTouchEvent(event); |
| OnTouchEventHandlingEnd(event); |
| uma_histogram_.RecordTouchEvent(event); |
| return true; |
| } |
| |
| void GestureProvider::ResetDetection() { |
| MotionEventGeneric generic_cancel_event(MotionEvent::ACTION_CANCEL, |
| base::TimeTicks::Now(), |
| PointerProperties()); |
| OnTouchEvent(generic_cancel_event); |
| } |
| |
| void GestureProvider::SetMultiTouchZoomSupportEnabled(bool enabled) { |
| gesture_listener_->SetMultiTouchZoomEnabled(enabled); |
| } |
| |
| void GestureProvider::SetDoubleTapSupportForPlatformEnabled(bool enabled) { |
| if (double_tap_support_for_platform_ == enabled) |
| return; |
| double_tap_support_for_platform_ = enabled; |
| UpdateDoubleTapDetectionSupport(); |
| } |
| |
| void GestureProvider::SetDoubleTapSupportForPageEnabled(bool enabled) { |
| if (double_tap_support_for_page_ == enabled) |
| return; |
| double_tap_support_for_page_ = enabled; |
| UpdateDoubleTapDetectionSupport(); |
| } |
| |
| bool GestureProvider::IsScrollInProgress() const { |
| return gesture_listener_->IsScrollInProgress(); |
| } |
| |
| bool GestureProvider::IsPinchInProgress() const { |
| return gesture_listener_->IsPinchInProgress(); |
| } |
| |
| bool GestureProvider::IsDoubleTapInProgress() const { |
| return gesture_listener_->IsDoubleTapInProgress(); |
| } |
| |
| bool GestureProvider::CanHandle(const MotionEvent& event) const { |
| // Aura requires one cancel event per touch point, whereas Android requires |
| // one cancel event per touch sequence. Thus we need to allow extra cancel |
| // events. |
| return current_down_event_ || event.GetAction() == MotionEvent::ACTION_DOWN || |
| event.GetAction() == MotionEvent::ACTION_CANCEL; |
| } |
| |
| void GestureProvider::OnTouchEventHandlingBegin(const MotionEvent& event) { |
| switch (event.GetAction()) { |
| case MotionEvent::ACTION_DOWN: |
| current_down_event_ = event.Clone(); |
| if (gesture_begin_end_types_enabled_) |
| gesture_listener_->Send( |
| gesture_listener_->CreateGesture(ET_GESTURE_BEGIN, event)); |
| break; |
| case MotionEvent::ACTION_POINTER_DOWN: |
| if (gesture_begin_end_types_enabled_) { |
| const int action_index = event.GetActionIndex(); |
| gesture_listener_->Send(gesture_listener_->CreateGesture( |
| ET_GESTURE_BEGIN, |
| event.GetPointerId(), |
| event.GetToolType(), |
| event.GetEventTime(), |
| event.GetX(action_index), |
| event.GetY(action_index), |
| event.GetRawX(action_index), |
| event.GetRawY(action_index), |
| event.GetPointerCount(), |
| gesture_listener_->GetBoundingBox(event, ET_GESTURE_BEGIN), |
| event.GetFlags())); |
| } |
| break; |
| case MotionEvent::ACTION_POINTER_UP: |
| case MotionEvent::ACTION_UP: |
| case MotionEvent::ACTION_CANCEL: |
| case MotionEvent::ACTION_MOVE: |
| break; |
| } |
| } |
| |
| void GestureProvider::OnTouchEventHandlingEnd(const MotionEvent& event) { |
| switch (event.GetAction()) { |
| case MotionEvent::ACTION_UP: |
| case MotionEvent::ACTION_CANCEL: { |
| if (gesture_begin_end_types_enabled_) |
| gesture_listener_->Send( |
| gesture_listener_->CreateGesture(ET_GESTURE_END, event)); |
| |
| current_down_event_.reset(); |
| |
| UpdateDoubleTapDetectionSupport(); |
| break; |
| } |
| case MotionEvent::ACTION_POINTER_UP: |
| if (gesture_begin_end_types_enabled_) |
| gesture_listener_->Send( |
| gesture_listener_->CreateGesture(ET_GESTURE_END, event)); |
| break; |
| case MotionEvent::ACTION_DOWN: |
| case MotionEvent::ACTION_POINTER_DOWN: |
| case MotionEvent::ACTION_MOVE: |
| break; |
| } |
| } |
| |
| void GestureProvider::UpdateDoubleTapDetectionSupport() { |
| // The GestureDetector requires that any provided DoubleTapListener remain |
| // attached to it for the duration of a touch sequence. Defer any potential |
| // null'ing of the listener until the sequence has ended. |
| if (current_down_event_) |
| return; |
| |
| const bool double_tap_enabled = |
| double_tap_support_for_page_ && double_tap_support_for_platform_; |
| gesture_listener_->SetDoubleTapEnabled(double_tap_enabled); |
| } |
| |
| } // namespace ui |