Index: ios/chrome/browser/ui/tab_switcher/tab_switcher_tab_strip_placeholder_view.mm |
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_switcher_tab_strip_placeholder_view.mm b/ios/chrome/browser/ui/tab_switcher/tab_switcher_tab_strip_placeholder_view.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fda92e3d8ea2271b2b2eb0071f57700c6a8da20b |
--- /dev/null |
+++ b/ios/chrome/browser/ui/tab_switcher/tab_switcher_tab_strip_placeholder_view.mm |
@@ -0,0 +1,166 @@ |
+// Copyright 2016 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. |
+ |
+#include "ios/chrome/browser/ui/tab_switcher/tab_switcher_tab_strip_placeholder_view.h" |
+ |
+#include <algorithm> |
+ |
+#import <QuartzCore/QuartzCore.h> |
+ |
+#import "base/ios/weak_nsobject.h" |
+#include "base/mac/foundation_util.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "ios/chrome/browser/ui/rtl_geometry.h" |
+ |
+// Returns the animation drag coefficient set by the iPhone simulator. |
+// This is useful when debugging with the simulator because when |
+// "slow animation" mode is toggled, it only impacts UIKit animations not |
+// CoreAnimation animations. By reading the value of that |
+// UIAnimatonDragCoefficient API we can also slow down Core Animation animations |
+// with the right value. |
+// On device we always return 1.0 because it's using private API. |
+#if TARGET_IPHONE_SIMULATOR |
+UIKIT_EXTERN float UIAnimationDragCoefficient(); |
+float animationDragCoefficient() { |
+ return UIAnimationDragCoefficient(); |
+} |
+#else |
+float animationDragCoefficient() { |
+ return 1.0f; |
+} |
+#endif |
+ |
+@interface TabSwitcherTabStripPlaceholderView () { |
+ // YES when the fold animation is currently playing. |
+ BOOL _animatingFold; |
+} |
+ |
+// Adds a transform animation to |layer| with |duration|, |beginTime|, |
+// from value |from| to value |to|. The animation will be reversed if |reverse| |
+// is set to true. |
+- (void)addTransformAnimationToLayer:(CALayer*)layer |
+ duration:(CFTimeInterval)duration |
+ beginTime:(CFTimeInterval)beginTime |
+ from:(CATransform3D)from |
+ to:(CATransform3D)to |
+ reverse:(BOOL)reverse; |
+ |
+// Triggers a fold animation if |fold| is true and an unfold aniamtion if |fold| |
+// is false. The |completion| block is called at the end of the animation. |
+- (void)animateFold:(BOOL)fold withCompletion:(ProceduralBlock)completion; |
+ |
+@end |
+ |
+namespace { |
+// Tab strip fold animation total duration in seconds. |
+const CGFloat kFoldAnimationDuration = 0.25; |
+// Tab strip fold animation duration in seconds for a single tab view. |
+const CGFloat kTabFoldAnimationDuration = 0.15; |
+} |
+ |
+@implementation TabSwitcherTabStripPlaceholderView |
+ |
+- (instancetype)initWithFrame:(CGRect)frame { |
+ self = [super initWithFrame:frame]; |
+ if (self) { |
+ self.backgroundColor = [UIColor clearColor]; |
+ if (UseRTLLayout()) |
+ [self setTransform:CGAffineTransformMakeScale(-1, 1)]; |
+ } |
+ return self; |
+} |
+ |
+- (void)foldWithCompletion:(ProceduralBlock)completion { |
+ [self animateFold:YES withCompletion:completion]; |
+} |
+ |
+- (void)unfoldWithCompletion:(ProceduralBlock)completion { |
+ [self animateFold:NO withCompletion:completion]; |
+} |
+ |
+#pragma mark - Private |
+ |
+- (void)addTransformAnimationToLayer:(CALayer*)layer |
+ duration:(CFTimeInterval)duration |
+ beginTime:(CFTimeInterval)beginTime |
+ from:(CATransform3D)from |
+ to:(CATransform3D)to |
+ reverse:(BOOL)reverse { |
+ CABasicAnimation* transformAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"transform"]; |
+ transformAnimation.duration = animationDragCoefficient() * duration; |
+ transformAnimation.beginTime = beginTime; |
+ transformAnimation.fromValue = |
+ [NSValue valueWithCATransform3D:(reverse ? to : from)]; |
+ transformAnimation.toValue = |
+ [NSValue valueWithCATransform3D:(reverse ? from : to)]; |
+ transformAnimation.fillMode = kCAFillModeBoth; |
+ transformAnimation.removedOnCompletion = NO; |
+ [layer addAnimation:transformAnimation forKey:@"transform"]; |
+} |
+ |
+- (void)animateFold:(BOOL)fold withCompletion:(ProceduralBlock)completion { |
+ DCHECK(!_animatingFold); |
+ const BOOL reversed = !fold; |
+ _animatingFold = YES; |
+ [self setUserInteractionEnabled:NO]; |
+ [CATransaction begin]; |
+ base::WeakNSObject<TabSwitcherTabStripPlaceholderView> weakSelf(self); |
+ [CATransaction setCompletionBlock:^{ |
+ base::scoped_nsobject<TabSwitcherTabStripPlaceholderView> strongSelf( |
+ [weakSelf retain]); |
+ if (!strongSelf) { |
+ if (completion) |
+ completion(); |
+ return; |
+ } |
+ strongSelf.get()->_animatingFold = NO; |
+ [strongSelf setUserInteractionEnabled:YES]; |
+ if (completion) |
+ completion(); |
+ for (UIView* view in [strongSelf subviews]) { |
+ [view.layer removeAnimationForKey:@"transform"]; |
+ } |
+ }]; |
+ const CFTimeInterval currentTime = |
+ [self.layer convertTime:CACurrentMediaTime() fromLayer:nil]; |
+ const NSInteger tabCount = [[self subviews] count]; |
+ const CFTimeInterval deltaTimeBetweenAnimations = |
+ std::min((kFoldAnimationDuration - kTabFoldAnimationDuration) / tabCount, |
+ kTabFoldAnimationDuration); |
+ const BOOL isRTL = UseRTLLayout(); |
+ NSArray* orderedViews = [[self subviews] |
+ sortedArrayUsingComparator:^(UIView* view1, UIView* view2) { |
+ if (!isRTL) { |
+ if (CGRectGetMinX(view1.frame) < CGRectGetMinX(view2.frame)) |
+ return NSOrderedDescending; |
+ else |
+ return NSOrderedAscending; |
+ } else { |
+ if (CGRectGetMaxX(view1.frame) > CGRectGetMaxX(view2.frame)) |
+ return NSOrderedDescending; |
+ else |
+ return NSOrderedAscending; |
+ } |
+ }]; |
+ // Fold all subviews one by one with a small delay and following the current |
+ // UI layout direction. |
+ int index = 0; |
+ for (UIView* view in orderedViews) { |
+ CATransform3D transform = CATransform3DConcat( |
+ view.layer.transform, |
+ CATransform3DMakeTranslation(0, view.bounds.size.height, 0)); |
+ [self addTransformAnimationToLayer:view.layer |
+ duration:kTabFoldAnimationDuration |
+ beginTime:currentTime + |
+ index * deltaTimeBetweenAnimations |
+ from:view.layer.transform |
+ to:transform |
+ reverse:reversed]; |
+ ++index; |
+ } |
+ [CATransaction commit]; |
+} |
+ |
+@end |