[Fullscreen] Add a pinch-to-exit gesture
authorjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 22 Jun 2018 22:24:30 +0000 (22:24 +0000)
committerjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 22 Jun 2018 22:24:30 +0000 (22:24 +0000)
https://bugs.webkit.org/show_bug.cgi?id=186821

Reviewed by Tim Horton.

Add a pinch gesture recognizer that overrides the pan gesture recognizer when active. Hide the
WKFullscreenViewController's controls while a dismiss gesture is active.

* UIProcess/ios/fullscreen/WKFullScreenViewController.h:
* UIProcess/ios/fullscreen/WKFullScreenViewController.mm:
(-[WKFullScreenViewController setAnimating:]):
(-[WKFullScreenViewController prefersStatusBarHidden]):
(-[WKFullScreenViewController gestureRecognizer:shouldReceiveTouch:]):
(-[WKFullScreenViewController _touchDetected:]):
* UIProcess/ios/fullscreen/WKFullScreenWindowControllerIOS.mm:
(-[WKFullscreenAnimationController context]):
(-[WKFullscreenAnimationController updateWithProgress:scale:translation:anchor:]):
(-[WKFullScreenInteractiveTransition animator]):
(-[WKFullScreenInteractiveTransition updateInteractiveTransition:withScale:andTranslation:]):
(-[WKFullScreenWindowController enterFullScreen]):
(-[WKFullScreenWindowController beganExitFullScreenWithInitialFrame:finalFrame:]):
(-[WKFullScreenWindowController interactionControllerForDismissal:]):
(-[WKFullScreenWindowController _startToDismissFullscreenChanged:]):
(-[WKFullScreenWindowController _dismissFullscreenViewController]):
(-[WKFullScreenWindowController _interactiveDismissChanged:]):
(-[WKFullScreenWindowController _interactivePinchDismissChanged:]):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@233104 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit/ChangeLog
Source/WebKit/UIProcess/ios/fullscreen/WKFullScreenViewController.h
Source/WebKit/UIProcess/ios/fullscreen/WKFullScreenViewController.mm
Source/WebKit/UIProcess/ios/fullscreen/WKFullScreenWindowControllerIOS.mm

index 7d7cdce..d0b267b 100644 (file)
@@ -1,3 +1,32 @@
+2018-06-22  Jer Noble  <jer.noble@apple.com>
+
+        [Fullscreen] Add a pinch-to-exit gesture
+        https://bugs.webkit.org/show_bug.cgi?id=186821
+
+        Reviewed by Tim Horton.
+
+        Add a pinch gesture recognizer that overrides the pan gesture recognizer when active. Hide the
+        WKFullscreenViewController's controls while a dismiss gesture is active.
+
+        * UIProcess/ios/fullscreen/WKFullScreenViewController.h:
+        * UIProcess/ios/fullscreen/WKFullScreenViewController.mm:
+        (-[WKFullScreenViewController setAnimating:]):
+        (-[WKFullScreenViewController prefersStatusBarHidden]):
+        (-[WKFullScreenViewController gestureRecognizer:shouldReceiveTouch:]):
+        (-[WKFullScreenViewController _touchDetected:]):
+        * UIProcess/ios/fullscreen/WKFullScreenWindowControllerIOS.mm:
+        (-[WKFullscreenAnimationController context]):
+        (-[WKFullscreenAnimationController updateWithProgress:scale:translation:anchor:]):
+        (-[WKFullScreenInteractiveTransition animator]):
+        (-[WKFullScreenInteractiveTransition updateInteractiveTransition:withScale:andTranslation:]):
+        (-[WKFullScreenWindowController enterFullScreen]):
+        (-[WKFullScreenWindowController beganExitFullScreenWithInitialFrame:finalFrame:]):
+        (-[WKFullScreenWindowController interactionControllerForDismissal:]):
+        (-[WKFullScreenWindowController _startToDismissFullscreenChanged:]):
+        (-[WKFullScreenWindowController _dismissFullscreenViewController]):
+        (-[WKFullScreenWindowController _interactiveDismissChanged:]):
+        (-[WKFullScreenWindowController _interactivePinchDismissChanged:]):
+
 2018-06-22  Brian Burg  <bburg@apple.com>
 
         [Cocoa] REGRESSION(W3C): actions for key equivalents are not respected
index 89f298b..c2f6d8b 100644 (file)
@@ -40,6 +40,7 @@ NS_ASSUME_NONNULL_BEGIN
 @property (assign, nonatomic) BOOL prefersStatusBarHidden;
 @property (assign, nonatomic, getter=isPlaying) BOOL playing;
 @property (assign, nonatomic, getter=isPictureInPictureActive) BOOL pictureInPictureActive;
+@property (assign, nonatomic, getter=isAnimating) BOOL animating;
 
 - (id)initWithWebView:(WKWebView *)webView;
 - (void)showUI;
index 01d8965..6dce47b 100644 (file)
@@ -239,6 +239,19 @@ private:
     [_pipButton setSelected:active];
 }
 
+- (void)setAnimating:(BOOL)animating
+{
+    if (_animating == animating)
+        return;
+    _animating = animating;
+    [self setNeedsStatusBarAppearanceUpdate];
+
+    if (_animating)
+        [self hideUI];
+    else
+        [self showUI];
+}
+
 #pragma mark - UIViewController Overrides
 
 - (void)loadView
@@ -337,7 +350,7 @@ private:
 
 - (BOOL)prefersStatusBarHidden
 {
-    return _prefersStatusBarHidden;
+    return _animating || _prefersStatusBarHidden;
 }
 
 #pragma mark - UIGestureRecognizerDelegate
@@ -349,7 +362,8 @@ private:
 
 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
 {
-    [self showUI];
+    if (!self.animating)
+        [self showUI];
     return YES;
 }
 
@@ -408,7 +422,8 @@ private:
         if (score > requiredScore)
             [self _showPhishingAlert];
     }
-    [self showUI];
+    if (!self.animating)
+        [self showUI];
 }
 
 - (void)_statusBarFrameDidChange:(NSNotificationCenter *)notification
index 94433e1..d2590f1 100644 (file)
@@ -145,6 +145,7 @@ static const NSTimeInterval kAnimationDuration = 0.2;
 @property (nonatomic) CGRect initialFrame;
 @property (nonatomic) CGRect finalFrame;
 @property (nonatomic, getter=isAnimatingIn) BOOL animatingIn;
+@property (readonly, nonatomic) id<UIViewControllerContextTransitioning> context;
 @end
 
 @implementation WKFullscreenAnimationController {
@@ -167,6 +168,11 @@ static const NSTimeInterval kAnimationDuration = 0.2;
     [super dealloc];
 }
 
+- (id<UIViewControllerContextTransitioning>)context
+{
+    return _context.get();
+}
+
 - (void)_createViewsForTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext
 {
     _maskView = adoptNS([[UIView alloc] init]);
@@ -274,6 +280,17 @@ static const NSTimeInterval kAnimationDuration = 0.2;
     window.backgroundColor = [UIColor colorWithWhite:0 alpha:_initialBackgroundAlpha + progress * (_finalBackgroundAlpha - _initialBackgroundAlpha)];
 }
 
+- (void)updateWithProgress:(CGFloat)progress scale:(CGFloat)scale translation:(CGSize)translation anchor:(CGPoint)anchor
+{
+    CGAffineTransform progressTransform = _initialAnimatingViewTransform;
+    progressTransform = CGAffineTransformScale(progressTransform, scale, scale);
+    progressTransform = CGAffineTransformTranslate(progressTransform, translation.width, translation.height);
+    [_animatingView setTransform:progressTransform];
+
+    UIWindow *window = [_animatingView window];
+    window.backgroundColor = [UIColor colorWithWhite:0 alpha:_initialBackgroundAlpha + progress * (_finalBackgroundAlpha - _initialBackgroundAlpha)];
+}
+
 - (void)updateWithProgress:(CGFloat)progress translation:(CGSize)translation anchor:(CGPoint)anchor
 {
     CGAffineTransform progressTransform = _initialAnimatingViewTransform;
@@ -313,6 +330,7 @@ static const NSTimeInterval kAnimationDuration = 0.2;
 
 @interface WKFullScreenInteractiveTransition : NSObject<UIViewControllerInteractiveTransitioning>
 - (id)initWithAnimator:(WKFullscreenAnimationController *)animator anchor:(CGPoint)point;
+@property (nonatomic, readonly) WKFullscreenAnimationController *animator;
 @end
 
 @implementation WKFullScreenInteractiveTransition {
@@ -331,6 +349,11 @@ static const NSTimeInterval kAnimationDuration = 0.2;
     return self;
 }
 
+- (WKFullscreenAnimationController *)animator
+{
+    return _animator.get();
+}
+
 - (BOOL)wantsInteractiveStart
 {
     return YES;
@@ -348,6 +371,12 @@ static const NSTimeInterval kAnimationDuration = 0.2;
     [_context updateInteractiveTransition:progress];
 }
 
+- (void)updateInteractiveTransition:(CGFloat)progress withScale:(CGFloat)scale andTranslation:(CGSize)translation
+{
+    [_animator updateWithProgress:progress scale:scale translation:translation anchor:_anchor];
+    [_context updateInteractiveTransition:progress];
+}
+
 - (void)cancelInteractiveTransition
 {
     [_animator end:YES];
@@ -395,7 +424,8 @@ static const NSTimeInterval kAnimationDuration = 0.2;
     RetainPtr<UIViewController> _viewControllerForPresentation;
     RetainPtr<WKFullScreenViewController> _fullscreenViewController;
     RetainPtr<UISwipeGestureRecognizer> _startDismissGestureRecognizer;
-    RetainPtr<UIPanGestureRecognizer> _interactiveDismissGestureRecognizer;
+    RetainPtr<UIPanGestureRecognizer> _interactivePanDismissGestureRecognizer;
+    RetainPtr<UIPinchGestureRecognizer> _interactivePinchDismissGestureRecognizer;
     RetainPtr<WKFullScreenInteractiveTransition> _interactiveDismissTransitionCoordinator;
 
     CGRect _initialFrame;
@@ -480,17 +510,22 @@ static const NSTimeInterval kAnimationDuration = 0.2;
     _fullscreenViewController.get().view.frame = _rootViewController.get().view.bounds;
     [self _updateLocationInfo];
 
-    _startDismissGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc]  initWithTarget:self action:@selector(_startToDismissFullscreenChanged:)]);
+    _startDismissGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(_startToDismissFullscreenChanged:)]);
     [_startDismissGestureRecognizer setDelegate:self];
     [_startDismissGestureRecognizer setCancelsTouchesInView:YES];
     [_startDismissGestureRecognizer setNumberOfTouchesRequired:1];
     [_startDismissGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionDown];
     [_fullscreenViewController.get().view addGestureRecognizer:_startDismissGestureRecognizer.get()];
 
-    _interactiveDismissGestureRecognizer = adoptNS([[UIPanGestureRecognizer alloc]  initWithTarget:self action:@selector(_interactiveDismissChanged:)]);
-    [_interactiveDismissGestureRecognizer setDelegate:self];
-    [_interactiveDismissGestureRecognizer setCancelsTouchesInView:NO];
-    [_fullscreenViewController.get().view addGestureRecognizer:_interactiveDismissGestureRecognizer.get()];
+    _interactivePanDismissGestureRecognizer = adoptNS([[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_interactiveDismissChanged:)]);
+    [_interactivePanDismissGestureRecognizer setDelegate:self];
+    [_interactivePanDismissGestureRecognizer setCancelsTouchesInView:NO];
+    [_fullscreenViewController.get().view addGestureRecognizer:_interactivePanDismissGestureRecognizer.get()];
+
+    _interactivePinchDismissGestureRecognizer = adoptNS([[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(_interactivePinchDismissChanged:)]);
+    [_interactivePinchDismissGestureRecognizer setDelegate:self];
+    [_interactivePinchDismissGestureRecognizer setCancelsTouchesInView:NO];
+    [_fullscreenViewController.get().view addGestureRecognizer:_interactivePinchDismissGestureRecognizer.get()];
 
     [self _manager]->saveScrollPosition();
 
@@ -640,12 +675,7 @@ static const NSTimeInterval kAnimationDuration = 0.2;
         return;
     }
 
-    [_fullscreenViewController dismissViewControllerAnimated:YES completion:^{
-        if (![self._webView _page])
-            return;
-
-        [self _completedExitFullScreen];
-    }];
+    [self _dismissFullscreenViewController];
 }
 
 - (void)_completedExitFullScreen
@@ -774,8 +804,14 @@ static const NSTimeInterval kAnimationDuration = 0.2;
     if (![animator isKindOfClass:[WKFullscreenAnimationController class]])
         return nil;
 
-    if (!_interactiveDismissTransitionCoordinator)
-        _interactiveDismissTransitionCoordinator = adoptNS([[WKFullScreenInteractiveTransition alloc] initWithAnimator:(WKFullscreenAnimationController *)animator anchor:CGPointZero]);
+    if (!_interactiveDismissTransitionCoordinator) {
+        CGPoint anchor = CGPointZero;
+        if (_interactivePanDismissGestureRecognizer.get().state == UIGestureRecognizerStateBegan)
+            anchor = [_interactivePanDismissGestureRecognizer locationInView:_fullscreenViewController.get().view];
+        else if (_interactivePinchDismissGestureRecognizer.get().state == UIGestureRecognizerStateBegan)
+            anchor = [_interactivePinchDismissGestureRecognizer locationInView:_fullscreenViewController.get().view];
+        _interactiveDismissTransitionCoordinator = adoptNS([[WKFullScreenInteractiveTransition alloc] initWithAnimator:(WKFullscreenAnimationController *)animator anchor:anchor]);
+    }
 
     return _interactiveDismissTransitionCoordinator.get();
 }
@@ -901,13 +937,28 @@ static const NSTimeInterval kAnimationDuration = 0.2;
 
 - (void)_startToDismissFullscreenChanged:(id)sender
 {
+    if (_inInteractiveDismiss)
+        return;
     _inInteractiveDismiss = true;
+    [self _dismissFullscreenViewController];
+}
+
+- (void)_dismissFullscreenViewController
+{
+    [_fullscreenViewController setAnimating:YES];
     [_fullscreenViewController dismissViewControllerAnimated:YES completion:^{
         if (![self._webView _page])
             return;
 
+
+        if (_interactiveDismissTransitionCoordinator.get().animator.context.transitionWasCancelled) {
+            [_fullscreenViewController setAnimating:NO];
+            return;
+        }
+
+        _interactiveDismissTransitionCoordinator = nil;
+
         [self _completedExitFullScreen];
-        [_fullscreenViewController setPrefersStatusBarHidden:YES];
     }];
 }
 
@@ -916,26 +967,56 @@ static const NSTimeInterval kAnimationDuration = 0.2;
     if (!_inInteractiveDismiss)
         return;
 
-    CGPoint translation = [_interactiveDismissGestureRecognizer translationInView:_fullscreenViewController.get().view];
-    CGPoint velocity = [_interactiveDismissGestureRecognizer velocityInView:_fullscreenViewController.get().view];
+    auto pinchState = [_interactivePinchDismissGestureRecognizer state];
+    if (pinchState > UIGestureRecognizerStatePossible && pinchState <= UIGestureRecognizerStateEnded)
+        return;
+
+    CGPoint translation = [_interactivePanDismissGestureRecognizer translationInView:_fullscreenViewController.get().view];
+    CGPoint velocity = [_interactivePanDismissGestureRecognizer velocityInView:_fullscreenViewController.get().view];
     CGFloat progress = translation.y / (_fullscreenViewController.get().view.bounds.size.height / 2);
     progress = std::min(1., std::max(0., progress));
 
-    if (_interactiveDismissGestureRecognizer.get().state == UIGestureRecognizerStateEnded) {
+    if (_interactivePanDismissGestureRecognizer.get().state == UIGestureRecognizerStateEnded) {
         _inInteractiveDismiss = false;
 
         if (progress > 0.25 || (progress > 0 && velocity.y > 5))
             [self requestExitFullScreen];
-        else {
+        else
             [_interactiveDismissTransitionCoordinator cancelInteractiveTransition];
-            _interactiveDismissTransitionCoordinator = nil;
-        }
         return;
     }
 
     [_interactiveDismissTransitionCoordinator updateInteractiveTransition:progress withTranslation:CGSizeMake(translation.x, translation.y)];
 }
 
+- (void)_interactivePinchDismissChanged:(id)sender
+{
+    if (!_inInteractiveDismiss && _interactivePinchDismissGestureRecognizer.get().state == UIGestureRecognizerStateBegan) {
+        [self _startToDismissFullscreenChanged:sender];
+        return;
+    }
+
+    CGFloat scale = [_interactivePinchDismissGestureRecognizer scale];
+    CGFloat velocity = [_interactivePinchDismissGestureRecognizer velocity];
+    CGFloat progress = std::min(1., std::max(0., 1 - scale));
+
+    CGPoint translation = CGPointZero;
+    auto panState = [_interactivePanDismissGestureRecognizer state];
+    if (panState > UIGestureRecognizerStatePossible && panState <= UIGestureRecognizerStateEnded)
+        translation = [_interactivePanDismissGestureRecognizer translationInView:_fullscreenViewController.get().view];
+
+    if (_interactivePinchDismissGestureRecognizer.get().state == UIGestureRecognizerStateEnded) {
+        _inInteractiveDismiss = false;
+        if ((progress > 0.05 && velocity < 0.) || velocity < -2.5)
+            [self requestExitFullScreen];
+        else
+            [_interactiveDismissTransitionCoordinator cancelInteractiveTransition];
+        return;
+    }
+
+    [_interactiveDismissTransitionCoordinator updateInteractiveTransition:progress withScale:scale andTranslation:CGSizeMake(translation.x, translation.y)];
+}
+
 @end
 
 #endif // PLATFORM(IOS) && ENABLE(FULLSCREEN_API)