| // 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; |
| |
| base::WeakPtr<remoting::GestureInterpreter> _gestureInterpreter; |
| } |
| @end |
| |
| @implementation ClientGestures |
| |
| @synthesize delegate = _delegate; |
| |
| - (instancetype)initWithView:(UIView*)view client:(RemotingClient*)client { |
| _view = view; |
| _gestureInterpreter = client.gestureInterpreter->GetWeakPtr(); |
| |
| _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 { |
| if (!_gestureInterpreter) { |
| return; |
| } |
| |
| CGPoint pivot = [sender locationInView:_view]; |
| _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 { |
| if (!_gestureInterpreter) { |
| return; |
| } |
| |
| CGPoint touchPoint = [sender locationInView:_view]; |
| _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 (!_gestureInterpreter) { |
| return; |
| } |
| |
| if ([sender state] == UIGestureRecognizerStateChanged) { |
| CGPoint translation = [sender translationInView:_view]; |
| _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 (!_gestureInterpreter) { |
| return; |
| } |
| |
| if ([sender state] == UIGestureRecognizerStateEnded) { |
| CGPoint velocity = [sender velocityInView:_view]; |
| if (velocity.x != 0 || velocity.y != 0) { |
| _gestureInterpreter->OneFingerFling(velocity.x, velocity.y); |
| } |
| } |
| } |
| |
| // Handles the two finger scrolling gesture. |
| - (IBAction)scrollGestureTriggered:(UIPanGestureRecognizer*)sender { |
| if (!_gestureInterpreter) { |
| return; |
| } |
| |
| if ([sender state] == UIGestureRecognizerStateEnded) { |
| CGPoint velocity = [sender velocityInView:_view]; |
| _gestureInterpreter->ScrollWithVelocity(velocity.x, velocity.y); |
| return; |
| } |
| |
| CGPoint scrollPoint = [sender locationInView:_view]; |
| CGPoint translation = [sender translationInView:_view]; |
| _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 { |
| if (!_gestureInterpreter) { |
| return; |
| } |
| |
| CGPoint touchPoint = [sender locationInView:_view]; |
| _gestureInterpreter->Drag(touchPoint.x, touchPoint.y, |
| toGestureState([sender state])); |
| } |
| |
| - (IBAction)twoFingerTapGestureTriggered:(UITapGestureRecognizer*)sender { |
| if (!_gestureInterpreter) { |
| return; |
| } |
| |
| CGPoint touchPoint = [sender locationInView:_view]; |
| _gestureInterpreter->TwoFingerTap(touchPoint.x, touchPoint.y); |
| } |
| |
| - (IBAction)threeFingerTapGestureTriggered:(UITapGestureRecognizer*)sender { |
| if (!_gestureInterpreter) { |
| return; |
| } |
| |
| CGPoint touchPoint = [sender locationInView:_view]; |
| _gestureInterpreter->ThreeFingerTap(touchPoint.x, touchPoint.y); |
| } |
| |
| - (IBAction)threeFingerPanGestureTriggered:(UIPanGestureRecognizer*)sender { |
| if (!_gestureInterpreter) { |
| return; |
| } |
| |
| 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 { |
| if (!_gestureInterpreter) { |
| return; |
| } |
| |
| [_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 |