text: // Copyright 2014-2017 Omni Development, Inc. All rights reserved.
  //
  // This software may only be used and reproduced according to the
  // terms in the file OmniSourceLicense.html, which should be
  // distributed with this project and can also be found at
  // <http://www.omnigroup.com/developer/sourcecode/sourcelicense/>.
  
  #import <OmniUI/OUIVerticalSplitTransition.h>
  #import <UIKit/UIKit.h>
  
  RCS_ID("$Id$")
  
  @implementation OUIVerticalSplitTransition
  
  - (id)init;
  {
      if (!(self = [super init]))
          return nil;
      
      self.duration = 0.3;
      
      return self;
  }
  
  - (void)insertToViewIntoContainer:(id<UIViewControllerContextTransitioning>)transitionContext;
  {
      UIView *fromView = self.fromViewController.view;
      UIView *toView = self.toViewController.view;
      UIView *containerView = transitionContext.containerView;
      
      if (toView.superview == containerView)
          return;
  
      toView.frame = [transitionContext finalFrameForViewController:self.toViewController];
      
      [UIView performWithoutAnimation:^{
          if (self.operation == UINavigationControllerOperationPush) {
              [containerView insertSubview:toView aboveSubview:fromView];
          } else if (self.operation == UINavigationControllerOperationPop) {
              [containerView insertSubview:toView belowSubview:fromView];
          }
  
          [toView layoutIfNeeded];
      }];
  }
  
  - (void)didInsertViewIntoContainer:(id<UIViewControllerContextTransitioning>)transitionContext NS_REQUIRES_SUPER;
  {
      // No additional work to perform in the base class
  }
  
  - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext;
  {
      [super animateTransition:transitionContext];
      
      if (self.operation == UINavigationControllerOperationNone) {
          [transitionContext completeTransition:NO];
          return;
      }
      
      UIView *transitionView = [self _transitionView];
      if (transitionView == nil) {
          [transitionContext completeTransition:NO];
          return;
      }
      
      CGRect topRect = CGRectZero, bottomRect = CGRectZero;
      CGRect transitionFrame = [self _visibleFrameForTransitionViewsInContext:transitionContext];
      if (CGRectEqualToRect(transitionFrame, CGRectZero)) {
          [transitionContext completeTransition:NO];
          return;
      }
      
      [self insertToViewIntoContainer:transitionContext];
      [self didInsertViewIntoContainer:transitionContext];
  
      CGRect leftover;
      CGRect splitExclusionRect = (self.splitExcludingRectProvider != NULL) ? self.splitExcludingRectProvider() : CGRectZero;
      splitExclusionRect = [transitionContext.containerView convertRect:splitExclusionRect fromView:transitionView];
      CGRectDivide(transitionFrame, &topRect, &leftover, CGRectGetMinY(splitExclusionRect), CGRectMinYEdge);
      CGRectDivide(transitionFrame, &leftover, &bottomRect, CGRectGetMaxY(splitExclusionRect), CGRectMinYEdge);
      
      CGFloat topSpaceAdjustment = 0.0f;
      switch(self.operation) {
          case UINavigationControllerOperationPush: topSpaceAdjustment = self.fromViewController.view.safeAreaInsets.top; break;
          case UINavigationControllerOperationPop: topSpaceAdjustment = self.toViewController.view.safeAreaInsets.top; break;
          default: OBASSERT_NOT_REACHED("Unexpected navigation operation"); break;
      }
      
      CGRect shiftedTopRect = CGRectOffset(topRect, 0, -topRect.size.height + topSpaceAdjustment);
      CGRect shiftedBottomRect = CGRectOffset(bottomRect, 0, bottomRect.size.height);
      
      BOOL waitForScreenUpdates = (self.operation == UINavigationControllerOperationPop);
      
      UIView *containerView = [transitionContext containerView];
      UIView *backdropView = [[UIView alloc] initWithFrame:containerView.bounds];
      backdropView.backgroundColor = [UIColor blackColor];
      backdropView.alpha = [self _initialBackdropOpacity:transitionContext];
      
      self.topSnapshot = [transitionView resizableSnapshotViewFromRect:[transitionView convertRect:topRect fromView:containerView] afterScreenUpdates:waitForScreenUpdates withCapInsets:UIEdgeInsetsZero];
      self.topSnapshot.backgroundColor = self.snapshotBackgroundColor;
      
      self.bottomSnapshot = [transitionView resizableSnapshotViewFromRect:[transitionView convertRect:bottomRect fromView:containerView] afterScreenUpdates:waitForScreenUpdates withCapInsets:UIEdgeInsetsZero];
      self.bottomSnapshot.backgroundColor = self.snapshotBackgroundColor;
  
  #if 0 && defined(DEBUG_correia)
      self.topSnapshot.layer.borderColor = [UIColor orangeColor].CGColor;
      self.topSnapshot.layer.borderWidth = 2;
      self.bottomSnapshot.layer.borderColor = [UIColor orangeColor].CGColor;
      self.bottomSnapshot.layer.borderWidth = 2;
  #endif
      
      UIView *topShadowView = [self _shadowViewWithFrame:_topSnapshot.frame];
      UIView *bottomShadowView = [self _shadowViewWithFrame:_bottomSnapshot.frame];
      
      if (self.operation == UINavigationControllerOperationPush) {
          _topSnapshot.frame = topRect;
          topShadowView.frame = topRect;
          _bottomSnapshot.frame = bottomRect;
          bottomShadowView.frame = bottomRect;
      } else if (self.operation == UINavigationControllerOperationPop) {
          _topSnapshot.frame = shiftedTopRect;
          topShadowView.frame = shiftedTopRect;
          _bottomSnapshot.frame = shiftedBottomRect;
          bottomShadowView.frame = shiftedBottomRect;
      } else {
          OBASSERT_NOT_REACHED(@"Unexpected navigation controller operation");
          [transitionContext completeTransition:NO];
          return;
      }
      
      [UIView performWithoutAnimation:^{
          [containerView addSubview:backdropView];
          
          [containerView addSubview:topShadowView];
          [containerView addSubview:bottomShadowView];
          
          [containerView addSubview:_topSnapshot];
          [containerView addSubview:_bottomSnapshot];
      }];
      
      if (!CGRectIsEmpty(splitExclusionRect)) {
          if (_fadeType == FadedPortionFadesInPlace) {
              if (self.operation == UINavigationControllerOperationPush) {
                  [self fadeOutRect:splitExclusionRect inContext:transitionContext];
              } else {
                  [self fadeInRect:splitExclusionRect inContext:transitionContext];
              }
          } else if (_fadeType == FadedPortionSlidesInFromTop) {
              CGRect fadeToRect = [containerView convertRect:splitExclusionRect toView:transitionView];
              fadeToRect.origin.y = CGRectGetMaxY(shiftedTopRect);
              
              if (self.destinationRectHeightProvider != NULL) {
                  CGFloat destinationRectHeight = self.destinationRectHeightProvider();
                  if (destinationRectHeight > 0) {
                      fadeToRect.size.height = destinationRectHeight;
                  }
              }
  
              if (self.operation == UINavigationControllerOperationPush) {
                  [self crossFadeFromRect:splitExclusionRect toRect:fadeToRect inContext:transitionContext];
              } else {
                  [self crossFadeFromRect:fadeToRect toRect:splitExclusionRect inContext:transitionContext];
              }
          }
      }
      
      [UIView animateWithDuration:self.duration delay:0 options:0 animations:^{
          backdropView.alpha = [self _finalBackdropOpacity:transitionContext];
          topShadowView.alpha = (self.operation == UINavigationControllerOperationPop) ? 1.0 : 0.0;
          bottomShadowView.alpha = (self.operation == UINavigationControllerOperationPop) ? 1.0 : 0.0;
          
          if (self.operation == UINavigationControllerOperationPush) {
              _topSnapshot.frame = shiftedTopRect;
              topShadowView.frame = shiftedTopRect;
              _bottomSnapshot.frame = shiftedBottomRect;
              bottomShadowView.frame = shiftedBottomRect;
          } else if (self.operation == UINavigationControllerOperationPop) {
              _topSnapshot.frame = topRect;
              topShadowView.frame = topRect;
              _bottomSnapshot.frame = bottomRect;
              bottomShadowView.frame = bottomRect;
          } else {
              OBASSERT_NOT_REACHED(@"Unexpected navigation controller operation");
          }
          
      } completion:^(BOOL finished) {
          OBASSERT(finished, @"The animation should always finish – even if the backing transition doesn't");
          
          [backdropView removeFromSuperview];
          
          [_topSnapshot removeFromSuperview];
          [topShadowView removeFromSuperview];
          
          [_bottomSnapshot removeFromSuperview];
          [bottomShadowView removeFromSuperview];
          
          [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
      }];
  }
  
  - (void)animationEnded:(BOOL)transitionCompleted;
  {
      if (transitionCompleted && self.operation == UINavigationControllerOperationPop)
          [[self.fromViewController view] removeFromSuperview];
  }
  
  #pragma mark - Private
  
  - (UIView *)_transitionView;
  {
      if (self.operation == UINavigationControllerOperationPush) {
          return [self.fromViewController view];
      } else if (self.operation == UINavigationControllerOperationPop) {
          return [self.toViewController view];
      } else {
          OBASSERT_NOT_REACHED(@"Unexpected navigation controller operation");
          return nil;
      }
  }
  
  - (UIView *)_shadowViewWithFrame:(CGRect)frame;
  {
      UIView *shadowView = [[UIView alloc] initWithFrame:frame];
      shadowView.opaque = YES;
      shadowView.backgroundColor = [UIColor whiteColor];
      shadowView.layer.shadowOffset = CGSizeZero;
      shadowView.layer.shadowOpacity = self.shadowOpacity;
      shadowView.alpha = (self.operation == UINavigationControllerOperationPop) ? 0.0 : 1.0;
      return shadowView;
  }
  
  - (CGFloat)_initialBackdropOpacity:(id< UIViewControllerContextTransitioning>)transitionContext;
  {
      switch (self.operation) {
          case UINavigationControllerOperationPush: return self.backdropOpacity;
          case UINavigationControllerOperationPop: return 0.0f;
          default: OBASSERT_NOT_REACHED("Unexpected navigation operation"); return 1.0f;
      }
  }
  
  - (CGFloat)_finalBackdropOpacity:(id <UIViewControllerContextTransitioning>)transitionContext;
  {
      switch (self.operation) {
          case UINavigationControllerOperationPush: return 0.0f;
          case UINavigationControllerOperationPop: return self.backdropOpacity;
          default: OBASSERT_NOT_REACHED("Unexpected navigation operation"); return 1.0f;
      }
  }
  
  @end
  
  #pragma mark -
  
  @implementation OUIVerticalSplitTransition (Subclass)
  
  - (UIColor *)snapshotBackgroundColor;
  {
      return [UIColor whiteColor];
  }
  
  - (CGFloat)backdropOpacity;
  {
      return 0.09f;
  }
  
  - (CGFloat)shadowOpacity;
  {
      return 0.2f;
  }
  
  @end
  