/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et tw=80 : */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_layers_GestureEventListener_h #define mozilla_layers_GestureEventListener_h #include // for uint64_t #include "InputData.h" // for MultiTouchInput, etc #include "Units.h" // for ScreenIntPoint #include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 #include "mozilla/EventForwards.h" // for nsEventStatus #include "nsAutoPtr.h" // for nsRefPtr #include "nsISupportsImpl.h" #include "nsTArray.h" // for nsTArray class CancelableTask; namespace mozilla { namespace layers { class AsyncPanZoomController; /** * Platform-non-specific, generalized gesture event listener. This class * intercepts all touches events on their way to AsyncPanZoomController and * determines whether or not they are part of a gesture. * * For example, seeing that two fingers are on the screen means that the user * wants to do a pinch gesture, so we don't forward the touches along to * AsyncPanZoomController since it will think that they are just trying to pan * the screen. Instead, we generate a PinchGestureInput and send that. If the * touch event is not part of a gesture, we just return nsEventStatus_eIgnore * and AsyncPanZoomController is expected to handle it. * * Android doesn't use this class because it has its own built-in gesture event * listeners that should generally be preferred. */ class GestureEventListener { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GestureEventListener) GestureEventListener(AsyncPanZoomController* aAsyncPanZoomController); ~GestureEventListener(); // -------------------------------------------------------------------------- // These methods must only be called on the controller/UI thread. // /** * General input handler for a touch event. If the touch event is not a part * of a gesture, then we pass it along to AsyncPanZoomController. Otherwise, * it gets consumed here and never forwarded along. */ nsEventStatus HandleInputEvent(const InputData& aEvent); /** * Cancels any currently active gesture. May not properly handle situations * that require extra work at the gesture's end, like a pinch which only * requests a repaint once it has ended. */ void CancelGesture(); /** * Returns the AsyncPanZoomController stored on this class and used for * callbacks. */ AsyncPanZoomController* GetAsyncPanZoomController(); protected: enum GestureState { // There's no gesture going on, and we don't think we're about to enter one. GESTURE_NONE, // We have detected that two or more fingers are on the screen, but there // hasn't been enough movement yet to make us start actually zooming the // screen. GESTURE_WAITING_PINCH, // There are two or more fingers on the screen, and the user has already // pinched enough for us to start zooming the screen. GESTURE_PINCH, // A touch start has happened and it may turn into a tap. We use this // because, if we put down two fingers and then lift them very quickly, this // may be mistaken for a tap. GESTURE_WAITING_SINGLE_TAP, // A single tap has happened for sure, and we're waiting for a second tap. GESTURE_WAITING_DOUBLE_TAP, // A long tap has happened, wait for the tap to be released in case we need // to fire a click event in the case the long tap was not handled. GESTURE_LONG_TAP_UP }; /** * Attempts to handle the event as a pinch event. If it is not a pinch event, * then we simply tell the next consumer to consume the event instead. * * |aClearTouches| marks whether or not to terminate any pinch currently * happening. */ nsEventStatus HandlePinchGestureEvent(const MultiTouchInput& aEvent, bool aClearTouches); /** * Attempts to handle the event as a single tap event, which highlights links * before opening them. In general, this will not attempt to block the touch * event from being passed along to AsyncPanZoomController since APZC needs to * know about touches ending (and we only know if a touch was a tap once it * ends). */ nsEventStatus HandleSingleTapUpEvent(const MultiTouchInput& aEvent); /** * Attempts to handle a single tap confirmation. This is what will actually * open links, etc. In general, this will not attempt to block the touch event * from being passed along to AsyncPanZoomController since APZC needs to know * about touches ending (and we only know if a touch was a tap once it ends). */ nsEventStatus HandleSingleTapConfirmedEvent(const MultiTouchInput& aEvent); /** * Attempts to handle a long tap confirmation. This is what will use * for context menu. */ nsEventStatus HandleLongTapEvent(const MultiTouchInput& aEvent); /** * Attempts to handle release of long tap. This is used to fire click * events in the case the context menu was not invoked. */ nsEventStatus HandleLongTapUpEvent(const MultiTouchInput& aEvent); /** * Attempts to handle a tap event cancellation. This happens when we think * something was a tap but it actually wasn't. In general, this will not * attempt to block the touch event from being passed along to * AsyncPanZoomController since APZC needs to know about touches ending (and * we only know if a touch was a tap once it ends). */ nsEventStatus HandleTapCancel(const MultiTouchInput& aEvent); /** * Attempts to handle a double tap. This happens when we get two single taps * within a short time. In general, this will not attempt to block the touch * event from being passed along to AsyncPanZoomController since APZC needs to * know about touches ending (and we only know if a touch was a double tap * once it ends). */ nsEventStatus HandleDoubleTap(const MultiTouchInput& aEvent); /** * Times out a single tap we think may be turned into a double tap. This will * also send a single tap if we're still in the "GESTURE_WAITING_DOUBLE_TAP" * state when this is called. This should be called a short time after a * single tap is detected, and the delay on it should be enough that the user * has time to tap again (to make a double tap). */ void TimeoutDoubleTap(); /** * Times out a long tap. This should be called a 'long' time after a single * tap is detected. */ void TimeoutLongTap(); nsRefPtr mAsyncPanZoomController; /** * Array containing all active touches. When a touch happens it, gets added to * this array, even if we choose not to handle it. When it ends, we remove it. */ nsTArray mTouches; /** * Current gesture we're dealing with. */ GestureState mState; /** * Total change in span since we detected a pinch gesture. Only used when we * are in the |GESTURE_WAITING_PINCH| state and need to know how far zoomed * out we are compared to our original pinch span. Note that this does _not_ * continue to be updated once we jump into the |GESTURE_PINCH| state. */ float mSpanChange; /** * Previous span calculated for the purposes of setting inside a * PinchGestureInput. */ float mPreviousSpan; /** * Stores the time a touch started, used for detecting a tap gesture. Only * valid when there's exactly one touch in mTouches. This is the time that the * first touch was inserted into the array. This is a uint64_t because it is * initialized from interactions with InputData, which stores its timestamps as * a uint64_t. */ uint64_t mTapStartTime; /** * Stores the time the last tap ends (finger leaves the screen). This is used * when mDoubleTapTimeoutTask cannot be scheduled in time and consecutive * taps are falsely regarded as double taps. */ uint64_t mLastTapEndTime; /** * Cached copy of the last touch input, only valid when in the * "GESTURE_WAITING_DOUBLE_TAP" state. This is used to forward along to * AsyncPanZoomController if a single tap needs to be sent (since it is sent * shortly after the user actually taps, since we need to wait for a double * tap). */ MultiTouchInput mLastTouchInput; /** * Task used to timeout a double tap. This gets posted to the UI thread such * that it runs a short time after a single tap happens. We cache it so that * we can cancel it if a double tap actually comes in. * CancelDoubleTapTimeoutTask: Cancel the mDoubleTapTimeoutTask and also set * it to null. */ CancelableTask *mDoubleTapTimeoutTask; inline void CancelDoubleTapTimeoutTask(); /** * Task used to timeout a long tap. This gets posted to the UI thread such * that it runs a time when a single tap happens. We cache it so that * we can cancel it if any other touch event happens. * CancelLongTapTimeoutTask: Cancel the mLongTapTimeoutTask and also set * it to null. */ CancelableTask *mLongTapTimeoutTask; inline void CancelLongTapTimeoutTask(); /** * Position of the last touch starting. This is only valid during an attempt * to determine if a touch is a tap. This means that it is used in both the * "GESTURE_WAITING_SINGLE_TAP" and "GESTURE_WAITING_DOUBLE_TAP" states. */ ScreenIntPoint mTouchStartPosition; }; } } #endif