Bug 851128 - Introduce custom recognition for the double-tap gesture. r=smichaud
authorGuilherme de Araujo <guimdearaujo@gmail.com>, Brandon Waterloo <brandon.waterloo@gmail.com>
Wed, 10 Apr 2013 14:38:26 -0400
changeset 128501 29a5fd2889f3e703c07ac2241accadfbb0fb4059
parent 128500 ed313842afecdb3df56769e7a9672f8e95e1427c
child 128502 8165938b2dd4eb91968e5cb1a8536c76ad14a1bb
push id26346
push userryanvm@gmail.com
push dateThu, 11 Apr 2013 22:05:52 +0000
treeherdermozilla-inbound@c2943880a742 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmichaud
bugs851128
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 851128 - Introduce custom recognition for the double-tap gesture. r=smichaud Cocoa does not implement the double-tap gesture by default, so the gesture had to be recognized manually.
dom/interfaces/events/nsIDOMSimpleGestureEvent.idl
widget/cocoa/nsChildView.h
widget/cocoa/nsChildView.mm
--- a/dom/interfaces/events/nsIDOMSimpleGestureEvent.idl
+++ b/dom/interfaces/events/nsIDOMSimpleGestureEvent.idl
@@ -74,17 +74,16 @@
  * the rotation gesture is complete, you only need to hook this event
  * and can safely ignore the MozRotateGestureStart and the
  * MozRotateGestureUpdate events.  The "delta" value is the cumulative
  * amount of rotation represented by the user's gesture.
  *
  * MozTapGesture - Generated when the user executes a two finger
  * tap gesture on the input device. Client coordinates contain the
  * center point of the tap.
- * (XXX Not implemented on Mac)
  *
  * MozPressTapGesture - Generated when the user executes a press
  * and tap two finger gesture (first finger down, second finger down,
  * second finger up, first finger up) on the input device.
  * Client coordinates contain the center pivot point of the action.
  * (XXX Not implemented on Mac)
  *
  * MozEdgeUIGesture - Generated when the user swipes the display to
--- a/widget/cocoa/nsChildView.h
+++ b/widget/cocoa/nsChildView.h
@@ -249,21 +249,29 @@ typedef NSInteger NSEventGestureAxis;
   //
   // mCumulativeRotation keeps track of the total amount of rotation
   // performed during a rotate gesture so we can send that value with
   // the final MozRotateGesture event.
   enum {
     eGestureState_None,
     eGestureState_StartGesture,
     eGestureState_MagnifyGesture,
-    eGestureState_RotateGesture
+    eGestureState_RotateGesture,
+    eGestureState_TapGesture
   } mGestureState;
   float mCumulativeMagnification;
   float mCumulativeRotation;
 
+  // Custom double tap gesture support
+  //
+  // mFirstTapTime keeps track of the time when the first tap occured
+  // and is used to check whether second tap should be recognized as
+  // a double tap gesture.
+  NSTimeInterval mFirstTapTime;
+
   BOOL mDidForceRefreshOpenGL;
   BOOL mWaitingForPaint;
 
 #ifdef __LP64__
   // Support for fluid swipe tracking.
   void (^mCancelSwipeAnimation)();
 #endif
 
@@ -320,16 +328,20 @@ typedef NSInteger NSEventGestureAxis;
 // The prototypes were obtained from the following link:
 // http://cocoadex.com/2008/02/nsevent-modifications-swipe-ro.html
 - (void)swipeWithEvent:(NSEvent *)anEvent;
 - (void)beginGestureWithEvent:(NSEvent *)anEvent;
 - (void)magnifyWithEvent:(NSEvent *)anEvent;
 - (void)rotateWithEvent:(NSEvent *)anEvent;
 - (void)endGestureWithEvent:(NSEvent *)anEvent;
 
+// Not a genuine nsResponder method, but called by touchesBeganWithEvent
+// to simulate double-tap recognition
+- (void)tapWithEvent:(NSEvent *)anEvent;
+
 // Support for fluid swipe tracking.
 #ifdef __LP64__
 - (void)maybeTrackScrollEventAsSwipe:(NSEvent *)anEvent
                       scrollOverflow:(double)overflow;
 #endif
 
 - (void)setUsingOMTCompositor:(BOOL)aUseOMTC;
 @end
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -2196,16 +2196,18 @@ NSEvent* gLastDragMouseDownEvent = nil;
 #endif
     mPendingDisplay = NO;
     mBlockedLastMouseDown = NO;
 
     mLastMouseDownEvent = nil;
     mClickThroughMouseDownEvent = nil;
     mDragService = nullptr;
 
+    [self setAcceptsTouchEvents:YES];
+
     mGestureState = eGestureState_None;
     mCumulativeMagnification = 0.0;
     mCumulativeRotation = 0.0;
 
     // We can't call forceRefreshOpenGL here because, in order to work around
     // the bug, it seems we need to have a draw already happening. Therefore,
     // we call it in drawRect:inContext:, when we know that a draw is in
     // progress.
@@ -3100,20 +3102,99 @@ NSEvent* gLastDragMouseDownEvent = nil;
 
 /*
  * XXX - The swipeWithEvent, beginGestureWithEvent, magnifyWithEvent,
  * rotateWithEvent, and endGestureWithEvent methods are part of a
  * PRIVATE interface exported by nsResponder and reverse-engineering
  * was necessary to obtain the methods' prototypes. Thus, Apple may
  * change the interface in the future without notice.
  *
+ * XXX - The tapWithEvent is a custom gesture that is set up below.
+ *       Cocoa doesn't recognize double-taps by default, so the
+ *       recognition is done mainly by touchesBeganWithEvent.
+ *
  * The prototypes were obtained from the following link:
  * http://cocoadex.com/2008/02/nsevent-modifications-swipe-ro.html
  */
 
+- (void)touchesBeganWithEvent:(NSEvent *)anEvent
+{
+  if (!anEvent) {
+    return;
+  }
+
+  // Set up for recognition of a double tap gesture
+  NSSet* touches =
+    [anEvent touchesMatchingPhase:NSTouchPhaseTouching inView:self];
+  NSUInteger touchCount = [touches count];
+  if (touchCount != 2) {
+    // Cancel double tap if 3+ fingers touch
+    if (mGestureState == eGestureState_TapGesture && touchCount > 2) {
+      mGestureState = eGestureState_None;
+    }
+    return;
+  }
+
+  if (mGestureState == eGestureState_TapGesture) {
+    NSTimeInterval deltaTapTime =
+      [NSDate timeIntervalSinceReferenceDate] - mFirstTapTime;
+    if (deltaTapTime <= [NSEvent doubleClickInterval] &&
+        deltaTapTime > 0.00) {
+      [self tapWithEvent: anEvent];
+      return;
+    }
+  }
+  mGestureState = eGestureState_TapGesture;
+  mFirstTapTime = [NSDate timeIntervalSinceReferenceDate];
+}
+
+- (void)touchesMovedWithEvent:(NSEvent *)anEvent
+{
+  // Cancel double tap if there's movement
+  if (mGestureState == eGestureState_TapGesture) {
+    mGestureState = eGestureState_None;
+  }
+}
+
+- (void)touchesEndedWithEvent:(NSEvent *)anEvent
+{
+  return;
+}
+
+- (void)touchesCancelledWithEvent:(NSEvent *)anEvent
+{
+  // Clear the gestures state.
+  mGestureState = eGestureState_None;
+}
+
+- (void)tapWithEvent:(NSEvent *)anEvent
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+  if (!anEvent) {
+    return;
+  }
+
+  nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+  // Setup the "double tap" event.
+  nsSimpleGestureEvent geckoEvent(true, NS_SIMPLE_GESTURE_TAP,
+                                  mGeckoChild, 0, 0.0);
+  [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
+  geckoEvent.clickCount = 1;
+
+  // Send the event.
+  mGeckoChild->DispatchWindowEvent(geckoEvent);
+
+  // Clear the gesture state
+  mGestureState = eGestureState_None;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
 - (void)swipeWithEvent:(NSEvent *)anEvent
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   if (!anEvent || !mGeckoChild)
     return;
 
   nsAutoRetainCocoaObject kungFuDeathGrip(self);
@@ -3174,16 +3255,17 @@ NSEvent* gLastDragMouseDownEvent = nil;
     break;
 
   case eGestureState_MagnifyGesture:
     msg = NS_SIMPLE_GESTURE_MAGNIFY_UPDATE;
     break;
 
   case eGestureState_None:
   case eGestureState_RotateGesture:
+  case eGestureState_TapGesture:
   default:
     return;
   }
 
   // Setup the event.
   nsSimpleGestureEvent geckoEvent(true, msg, mGeckoChild, 0, deltaZ);
   [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
 
@@ -3215,16 +3297,17 @@ NSEvent* gLastDragMouseDownEvent = nil;
     break;
 
   case eGestureState_RotateGesture:
     msg = NS_SIMPLE_GESTURE_ROTATE_UPDATE;
     break;
 
   case eGestureState_None:
   case eGestureState_MagnifyGesture:
+  case eGestureState_TapGesture:
   default:
     return;
   }
 
   // Setup the event.
   nsSimpleGestureEvent geckoEvent(true, msg, mGeckoChild, 0, 0.0);
   [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
   geckoEvent.delta = -rotation;
@@ -3284,16 +3367,17 @@ NSEvent* gLastDragMouseDownEvent = nil;
 
       // Send the event.
       mGeckoChild->DispatchWindowEvent(geckoEvent);
     }
     break;
 
   case eGestureState_None:
   case eGestureState_StartGesture:
+  case eGestureState_TapGesture:
   default:
     break;
   }
 
   // Clear the gestures state.
   mGestureState = eGestureState_None;
   mCumulativeMagnification = 0.0;
   mCumulativeRotation = 0.0;