| // 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. | 
 |  | 
 | #if !defined(__has_feature) || !__has_feature(objc_arc) | 
 | #error "This file requires ARC support." | 
 | #endif | 
 |  | 
 | #import "remoting/ios/client_gestures.h" | 
 |  | 
 | #import "remoting/ios/session/remoting_client.h" | 
 |  | 
 | #include "remoting/client/gesture_interpreter.h" | 
 |  | 
 | static remoting::GestureInterpreter::GestureState toGestureState( | 
 |     UIGestureRecognizerState state) { | 
 |   switch (state) { | 
 |     case UIGestureRecognizerStateBegan: | 
 |       return remoting::GestureInterpreter::GESTURE_BEGAN; | 
 |     case UIGestureRecognizerStateChanged: | 
 |       return remoting::GestureInterpreter::GESTURE_CHANGED; | 
 |     default: | 
 |       return remoting::GestureInterpreter::GESTURE_ENDED; | 
 |   } | 
 | } | 
 |  | 
 | @interface ClientGestures ()<UIGestureRecognizerDelegate> { | 
 |  @private | 
 |   UILongPressGestureRecognizer* _longPressRecognizer; | 
 |   UIPanGestureRecognizer* _panRecognizer; | 
 |   UIPanGestureRecognizer* _flingRecognizer; | 
 |   UIPanGestureRecognizer* _scrollRecognizer; | 
 |  | 
 |   // TODO(yuweih): Commented out because this makes two-finger gestures not | 
 |   // quite responsive. Clean these up if it's really unnecessary. | 
 |   //  UIPanGestureRecognizer* _threeFingerPanRecognizer; | 
 |  | 
 |   UIPinchGestureRecognizer* _pinchRecognizer; | 
 |   UITapGestureRecognizer* _singleTapRecognizer; | 
 |   UITapGestureRecognizer* _twoFingerTapRecognizer; | 
 |   UITapGestureRecognizer* _threeFingerTapRecognizer; | 
 |   UITapGestureRecognizer* _fourFingerTapRecognizer; | 
 |  | 
 |   __weak UIView* _view; | 
 |   __weak RemotingClient* _client; | 
 | } | 
 | @end | 
 |  | 
 | @implementation ClientGestures | 
 |  | 
 | @synthesize delegate = _delegate; | 
 |  | 
 | - (instancetype)initWithView:(UIView*)view client:(RemotingClient*)client { | 
 |   _view = view; | 
 |   _client = client; | 
 |  | 
 |   _longPressRecognizer = [[UILongPressGestureRecognizer alloc] | 
 |       initWithTarget:self | 
 |               action:@selector(longPressGestureTriggered:)]; | 
 |   _longPressRecognizer.delegate = self; | 
 |   [view addGestureRecognizer:_longPressRecognizer]; | 
 |  | 
 |   _panRecognizer = [[UIPanGestureRecognizer alloc] | 
 |       initWithTarget:self | 
 |               action:@selector(panGestureTriggered:)]; | 
 |   _panRecognizer.minimumNumberOfTouches = 1; | 
 |   _panRecognizer.maximumNumberOfTouches = 2; | 
 |   _panRecognizer.delegate = self; | 
 |   [view addGestureRecognizer:_panRecognizer]; | 
 |  | 
 |   _flingRecognizer = [[UIPanGestureRecognizer alloc] | 
 |       initWithTarget:self | 
 |               action:@selector(flingGestureTriggered:)]; | 
 |   _flingRecognizer.minimumNumberOfTouches = 1; | 
 |   _flingRecognizer.maximumNumberOfTouches = 1; | 
 |   _flingRecognizer.delegate = self; | 
 |   [view addGestureRecognizer:_flingRecognizer]; | 
 |  | 
 |   _scrollRecognizer = [[UIPanGestureRecognizer alloc] | 
 |       initWithTarget:self | 
 |               action:@selector(scrollGestureTriggered:)]; | 
 |   _scrollRecognizer.minimumNumberOfTouches = 2; | 
 |   _scrollRecognizer.maximumNumberOfTouches = 2; | 
 |   _scrollRecognizer.delegate = self; | 
 |   [view addGestureRecognizer:_scrollRecognizer]; | 
 |  | 
 |   //  _threeFingerPanRecognizer = [[UIPanGestureRecognizer alloc] | 
 |   //      initWithTarget:self | 
 |   //              action:@selector(threeFingerPanGestureTriggered:)]; | 
 |   //  _threeFingerPanRecognizer.minimumNumberOfTouches = 3; | 
 |   //  _threeFingerPanRecognizer.maximumNumberOfTouches = 3; | 
 |   //  _threeFingerPanRecognizer.delegate = self; | 
 |   //  [view addGestureRecognizer:_threeFingerPanRecognizer]; | 
 |  | 
 |   _pinchRecognizer = [[UIPinchGestureRecognizer alloc] | 
 |       initWithTarget:self | 
 |               action:@selector(pinchGestureTriggered:)]; | 
 |   _pinchRecognizer.delegate = self; | 
 |   [view addGestureRecognizer:_pinchRecognizer]; | 
 |  | 
 |   _singleTapRecognizer = [[UITapGestureRecognizer alloc] | 
 |       initWithTarget:self | 
 |               action:@selector(tapGestureTriggered:)]; | 
 |   _singleTapRecognizer.delegate = self; | 
 |   [view addGestureRecognizer:_singleTapRecognizer]; | 
 |  | 
 |   _twoFingerTapRecognizer = [[UITapGestureRecognizer alloc] | 
 |       initWithTarget:self | 
 |               action:@selector(twoFingerTapGestureTriggered:)]; | 
 |   _twoFingerTapRecognizer.numberOfTouchesRequired = 2; | 
 |   _twoFingerTapRecognizer.delegate = self; | 
 |   [view addGestureRecognizer:_twoFingerTapRecognizer]; | 
 |  | 
 |   _threeFingerTapRecognizer = [[UITapGestureRecognizer alloc] | 
 |       initWithTarget:self | 
 |               action:@selector(threeFingerTapGestureTriggered:)]; | 
 |   _threeFingerTapRecognizer.numberOfTouchesRequired = 3; | 
 |   _threeFingerTapRecognizer.delegate = self; | 
 |   [view addGestureRecognizer:_threeFingerTapRecognizer]; | 
 |  | 
 |   _fourFingerTapRecognizer = [[UITapGestureRecognizer alloc] | 
 |       initWithTarget:self | 
 |               action:@selector(fourFingerTapGestureTriggered:)]; | 
 |   _fourFingerTapRecognizer.numberOfTouchesRequired = 4; | 
 |   _fourFingerTapRecognizer.delegate = self; | 
 |   [view addGestureRecognizer:_fourFingerTapRecognizer]; | 
 |  | 
 |   [_singleTapRecognizer requireGestureRecognizerToFail:_twoFingerTapRecognizer]; | 
 |   [_twoFingerTapRecognizer | 
 |       requireGestureRecognizerToFail:_threeFingerTapRecognizer]; | 
 |   [_pinchRecognizer requireGestureRecognizerToFail:_singleTapRecognizer]; | 
 |   //  [_pinchRecognizer | 
 |   //  requireGestureRecognizerToFail:_threeFingerPanRecognizer]; | 
 |   [_panRecognizer requireGestureRecognizerToFail:_singleTapRecognizer]; | 
 |   //  [_threeFingerPanRecognizer | 
 |   //      requireGestureRecognizerToFail:_threeFingerTapRecognizer]; | 
 |   [_panRecognizer requireGestureRecognizerToFail:_scrollRecognizer]; | 
 |  | 
 |   return self; | 
 | } | 
 |  | 
 | // Resize the view of the desktop - Zoom in/out.  This can occur during a Pan. | 
 | - (IBAction)pinchGestureTriggered:(UIPinchGestureRecognizer*)sender { | 
 |   CGPoint pivot = [sender locationInView:_view]; | 
 |   _client.gestureInterpreter->Zoom(pivot.x, pivot.y, sender.scale, | 
 |                                    toGestureState([sender state])); | 
 |  | 
 |   sender.scale = 1.0;  // reset scale so next iteration is a relative ratio | 
 | } | 
 |  | 
 | - (IBAction)tapGestureTriggered:(UITapGestureRecognizer*)sender { | 
 |   CGPoint touchPoint = [sender locationInView:_view]; | 
 |   _client.gestureInterpreter->Tap(touchPoint.x, touchPoint.y); | 
 | } | 
 |  | 
 | // Change position of the viewport. This can occur during a pinch or long press. | 
 | - (IBAction)panGestureTriggered:(UIPanGestureRecognizer*)sender { | 
 |   if ([sender state] == UIGestureRecognizerStateChanged) { | 
 |     CGPoint translation = [sender translationInView:_view]; | 
 |     _client.gestureInterpreter->Pan(translation.x, translation.y); | 
 |  | 
 |     // Reset translation so next iteration is relative | 
 |     [sender setTranslation:CGPointZero inView:_view]; | 
 |   } | 
 | } | 
 |  | 
 | // Do fling on the viewport. This will happen at the end of the one-finger | 
 | // panning. | 
 | - (IBAction)flingGestureTriggered:(UIPanGestureRecognizer*)sender { | 
 |   if ([sender state] == UIGestureRecognizerStateEnded) { | 
 |     CGPoint velocity = [sender velocityInView:_view]; | 
 |     if (velocity.x != 0 || velocity.y != 0) { | 
 |       _client.gestureInterpreter->OneFingerFling(velocity.x, velocity.y); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | // Handles the two finger scrolling gesture. | 
 | - (IBAction)scrollGestureTriggered:(UIPanGestureRecognizer*)sender { | 
 |   if ([sender state] == UIGestureRecognizerStateEnded) { | 
 |     CGPoint velocity = [sender velocityInView:_view]; | 
 |     _client.gestureInterpreter->ScrollWithVelocity(velocity.x, velocity.y); | 
 |     return; | 
 |   } | 
 |  | 
 |   CGPoint scrollPoint = [sender locationInView:_view]; | 
 |   CGPoint translation = [sender translationInView:_view]; | 
 |   _client.gestureInterpreter->Scroll(scrollPoint.x, scrollPoint.y, | 
 |                                      translation.x, translation.y); | 
 |  | 
 |   // Reset translation so next iteration is relative | 
 |   [sender setTranslation:CGPointZero inView:_view]; | 
 | } | 
 |  | 
 | // Click-Drag mouse operation.  This can occur during a Pan. | 
 | - (IBAction)longPressGestureTriggered:(UILongPressGestureRecognizer*)sender { | 
 |   CGPoint touchPoint = [sender locationInView:_view]; | 
 |   _client.gestureInterpreter->Drag(touchPoint.x, touchPoint.y, | 
 |                                    toGestureState([sender state])); | 
 | } | 
 |  | 
 | - (IBAction)twoFingerTapGestureTriggered:(UITapGestureRecognizer*)sender { | 
 |   CGPoint touchPoint = [sender locationInView:_view]; | 
 |   _client.gestureInterpreter->TwoFingerTap(touchPoint.x, touchPoint.y); | 
 | } | 
 |  | 
 | - (IBAction)threeFingerTapGestureTriggered:(UITapGestureRecognizer*)sender { | 
 |   CGPoint touchPoint = [sender locationInView:_view]; | 
 |   _client.gestureInterpreter->ThreeFingerTap(touchPoint.x, touchPoint.y); | 
 | } | 
 |  | 
 | - (IBAction)threeFingerPanGestureTriggered:(UIPanGestureRecognizer*)sender { | 
 |   if ([sender state] != UIGestureRecognizerStateEnded) { | 
 |     return; | 
 |   } | 
 |  | 
 |   CGPoint translation = [sender translationInView:_view]; | 
 |   if (translation.y > 0) { | 
 |     // Swiped down - hide keyboard (for now) | 
 |     [_delegate keyboardShouldHide]; | 
 |   } else if (translation.y < 0) { | 
 |     // Swiped up - show keyboard | 
 |     [_delegate keyboardShouldShow]; | 
 |   } | 
 | } | 
 |  | 
 | // To trigger the menu. | 
 | - (IBAction)fourFingerTapGestureTriggered: | 
 |     (UILongPressGestureRecognizer*)sender { | 
 |   [_delegate menuShouldShow]; | 
 | } | 
 |  | 
 | #pragma mark - UIGestureRecognizerDelegate | 
 |  | 
 | // Allow panning and zooming to occur simultaneously. | 
 | // Allow panning and long press to occur simultaneously. | 
 | // Pinch requires 2 touches, and long press requires a single touch, so they are | 
 | // mutually exclusive regardless of if panning is the initiating gesture. | 
 | // Pinch and Scroll are both two-finger gestures. They are mutually exclusive | 
 | // and whatever comes first should disable the other gesture recognizer. | 
 | - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer | 
 |     shouldRecognizeSimultaneouslyWithGestureRecognizer: | 
 |         (UIGestureRecognizer*)otherGestureRecognizer { | 
 |   if (gestureRecognizer == _pinchRecognizer || | 
 |       (gestureRecognizer == _panRecognizer)) { | 
 |     if (otherGestureRecognizer == _pinchRecognizer || | 
 |         otherGestureRecognizer == _panRecognizer) { | 
 |       return YES; | 
 |     } | 
 |   } | 
 |  | 
 |   if (gestureRecognizer == _flingRecognizer || | 
 |       (gestureRecognizer == _panRecognizer)) { | 
 |     if (otherGestureRecognizer == _flingRecognizer || | 
 |         otherGestureRecognizer == _panRecognizer) { | 
 |       return YES; | 
 |     } | 
 |   } | 
 |  | 
 |   if (gestureRecognizer == _longPressRecognizer || | 
 |       gestureRecognizer == _panRecognizer) { | 
 |     if (otherGestureRecognizer == _longPressRecognizer || | 
 |         otherGestureRecognizer == _panRecognizer) { | 
 |       return YES; | 
 |     } | 
 |   } | 
 |  | 
 |   if (gestureRecognizer == _twoFingerTapRecognizer && | 
 |       otherGestureRecognizer == _longPressRecognizer) { | 
 |     return YES; | 
 |   } | 
 |  | 
 |   // TODO(nicholss): If we return NO here, it dismisses the other reconizers. | 
 |   // As we add more types of reconizers, they need to be accounted for in the | 
 |   // above logic. | 
 |   return NO; | 
 | } | 
 |  | 
 | @end |