// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/tabs/ui_bundled/background_tab_animation_view.h"
#import "base/check.h"
#import "ios/chrome/browser/shared/ui/util/animation_util.h"
#import "ios/chrome/browser/shared/ui/util/layout_guide_names.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/shared/ui/util/util_swift.h"
#import "ios/chrome/common/material_timing.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
namespace {
const CGFloat kImageSize = 28;
const CGFloat kMaxScale = 1.3;
const CGFloat kMinScale = 0.7;
CGFloat kRotationAngleInRadians = 20.0 / 180 * M_PI;
} // namespace
@interface BackgroundTabAnimationView ()
// Whether the animation is taking place in incognito.
@property(nonatomic, assign) BOOL incognito;
@end
@implementation BackgroundTabAnimationView
- (instancetype)initWithFrame:(CGRect)frame incognito:(BOOL)incognito {
self = [super initWithFrame:frame];
if (self) {
_incognito = incognito;
self.overrideUserInterfaceStyle =
incognito ? UIUserInterfaceStyleDark : UIUserInterfaceStyleUnspecified;
}
return self;
}
#pragma mark - Public
- (void)animateFrom:(CGPoint)originPoint
toTabGridButtonWithCompletion:(void (^)())completion {
DCHECK(self.superview);
CGPoint origin = [self.superview convertPoint:originPoint fromView:nil];
CGPoint destination = [self destinationPoint];
// It can be negative.
CGFloat xDiff = destination.x - origin.x;
CGFloat yDiff = origin.y - destination.y;
UIBezierPath* positionPath =
[self positionPathWithParentHeight:self.superview.frame.size.height
xDiff:xDiff
yDiff:yDiff
origin:origin
destination:destination];
[CATransaction begin];
[CATransaction setCompletionBlock:^{
completion();
}];
CAMediaTimingFunction* easeIn = MaterialTimingFunction(MaterialCurveEaseIn);
CGFloat timing =
[self animationDurationWithParentSize:self.superview.frame.size
xDiff:xDiff
yDiff:yDiff];
CAKeyframeAnimation* scaleAnimation =
[CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
scaleAnimation.values = @[ @(1), @(kMaxScale), @(kMinScale) ];
scaleAnimation.keyTimes = @[ @0, @0.25, @1 ];
scaleAnimation.timingFunction = easeIn;
scaleAnimation.duration = timing;
CAKeyframeAnimation* rotateAnimation =
[CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
rotateAnimation.values = @[ @(0), @(0), @(kRotationAngleInRadians) ];
rotateAnimation.keyTimes = @[ @0, @0.5, @1 ];
rotateAnimation.timingFunction = easeIn;
rotateAnimation.duration = timing;
CAKeyframeAnimation* fadeAnimation =
[CAKeyframeAnimation animationWithKeyPath:@"opacity"];
fadeAnimation.values = @[ @(1), @(1), @(0) ];
fadeAnimation.keyTimes = @[ @0, @0.9, @1 ];
fadeAnimation.timingFunction = easeIn;
fadeAnimation.duration = timing;
CAKeyframeAnimation* positionAnimation =
[CAKeyframeAnimation animationWithKeyPath:@"position"];
positionAnimation.path = positionPath.CGPath;
positionAnimation.duration = timing;
positionAnimation.timingFunction = easeIn;
[self.layer
addAnimation:AnimationGroupMake(@[
positionAnimation, rotateAnimation, fadeAnimation, scaleAnimation
])
forKey:@"OpenInNewTabAnimation"];
[CATransaction commit];
}
#pragma mark - UIView
- (void)didMoveToSuperview {
[super didMoveToSuperview];
if (self.subviews.count == 0) {
self.backgroundColor = [UIColor colorNamed:kBackgroundColor];
self.layer.shadowRadius = 20;
self.layer.shadowOpacity = 0.4;
self.layer.shadowOffset = CGSizeMake(0, 3);
self.layer.cornerRadius = 13;
UIImageView* linkImage = [[UIImageView alloc]
initWithImage:
[[UIImage imageNamed:@"open_new_tab_background"]
imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
linkImage.translatesAutoresizingMaskIntoConstraints = NO;
linkImage.tintColor = [UIColor colorNamed:kToolbarButtonColor];
[self addSubview:linkImage];
[linkImage.widthAnchor constraintEqualToConstant:kImageSize].active = YES;
[linkImage.heightAnchor constraintEqualToConstant:kImageSize].active = YES;
AddSameCenterConstraints(self, linkImage);
}
}
#pragma mark - Private
// Returns the destination point for the animation, in the superview
// coordinates.
- (CGPoint)destinationPoint {
DCHECK(self.layoutGuideCenter);
UILayoutGuide* tabGridButtonLayoutGuide =
[self.layoutGuideCenter makeLayoutGuideNamed:kTabSwitcherGuide];
// Note: adding to the superview, as adding to `self` breaks its layout.
[self.superview addLayoutGuide:tabGridButtonLayoutGuide];
CGRect frame = [tabGridButtonLayoutGuide layoutFrame];
return CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
}
// Returns the animation duration, based on the `parentSize` and the `yDiff` and
// `xDiff` between the origin and destination point. The animation is faster the
// closer the origin and destination are.
- (CGFloat)animationDurationWithParentSize:(CGSize)parentSize
xDiff:(CGFloat)xDiff
yDiff:(CGFloat)yDiff {
CGFloat parentWidth = parentSize.width;
CGFloat parentHeight = parentSize.height;
CGFloat parentViewDiagonal =
parentWidth * parentWidth + parentHeight * parentHeight;
CGFloat distance = xDiff * xDiff + yDiff * yDiff;
return 0.8 * sqrt(distance / parentViewDiagonal) + 0.2;
}
// Returns the BezierPath that should be followed by the animated view, based on
// the `parentSize` and the `yDiff` and `xDiff` between the `origin` and
// `destination` point.
- (UIBezierPath*)positionPathWithParentHeight:(CGFloat)parentHeight
xDiff:(CGFloat)xDiff
yDiff:(CGFloat)yDiff
origin:(CGPoint)origin
destination:(CGPoint)destination {
CGFloat absYDiff = fabs(yDiff);
CGFloat firstControlPointYDifference =
absYDiff > parentHeight / 2 ? parentHeight / 2 * (yDiff / absYDiff)
: yDiff;
CGPoint firstControlPoint = CGPointMake(
origin.x + xDiff * 0.5, origin.y + firstControlPointYDifference);
CGPoint secondControlPoint =
CGPointMake(destination.x, destination.y + yDiff / 2);
UIBezierPath* path = UIBezierPath.bezierPath;
[path moveToPoint:origin];
[path addCurveToPoint:destination
controlPoint1:firstControlPoint
controlPoint2:secondControlPoint];
return path;
}
@end