Index: chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.mm |
diff --git a/chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.mm b/chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f01efc9c929041d17407ffaa7d37878a6f5b6a5c |
--- /dev/null |
+++ b/chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.mm |
@@ -0,0 +1,293 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
Andre
2015/01/12 06:10:03
2015
erikchen
2015/01/12 19:18:58
Done.
|
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.h" |
+ |
+#include <QuartzCore/QuartzCore.h> |
+ |
+#include "base/mac/scoped_cftyperef.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/mac/sdk_forward_declarations.h" |
+ |
+namespace { |
+ |
+NSString* const kPrimaryWindowAnimationID = @"PrimaryWindowAnimationID"; |
+NSString* const kSnapshotWindowAnimationID = @"SnapshotWindowAnimationID"; |
+NSString* const kAnimationIDKey = @"AnimationIDKey"; |
+ |
+// This class has two simultaneous animations to resize and reposition layers. |
+// These animations must use the same timing function, otherwise there will be |
+// visual discordance. |
+NSString* const kTransformAnimationTimingFunction = |
+ kCAMediaTimingFunctionEaseInEaseOut; |
+ |
+} // namespace |
+ |
+@interface BrowserWindowEnterFullscreenTransition () { |
+ // The window which is undergoing the fullscreen transition. |
+ base::scoped_nsobject<NSWindow> _primaryWindow; |
+ |
+ // A layer that holds a snapshot of the original state of |_primaryWindow|. |
+ base::scoped_nsobject<CALayer> _snapshotLayer; |
+ |
+ // A temporary window that holds |_snapshotLayer|. |
+ base::scoped_nsobject<NSWindow> _snapshotWindow; |
+ |
+ // The animation applied to |_snapshotLayer|. |
+ base::scoped_nsobject<CAAnimationGroup> _snapshotAnimation; |
+ |
+ // The animation applied to the root layer of |_primaryWindow|. |
+ base::scoped_nsobject<CAAnimationGroup> _primaryWindowAnimation; |
+ |
+ // The frame of the |_primaryWindow| before the transition began. |
+ NSRect _primaryWindowInitialFrame; |
+ |
+ // The background color of |_primaryWindow| before the transition began. |
+ base::scoped_nsobject<NSColor> _primaryWindowInitialBackgroundColor; |
+ |
+ // Whether |_primaryWindow| was opaque before the transition began. |
+ BOOL _primaryWindowInitialOpaque; |
+ |
+ // Whether the instance is in the process of changing the size of |
+ // |_primaryWindow|. |
+ BOOL _changingPrimaryWindowSize; |
+ |
+ // The frame that |_primaryWindow| is expected to have after the transition |
+ // is finished. |
+ NSRect _primaryWindowFinalFrame; |
+} |
+ |
+// Takes a snapshot of |_primaryWindow| and puts it in |_snapshotLayer|. |
+- (void)takeSnapshot; |
+ |
+// Creates |_snapshotWindow| and adds |_snapshotLayer| to it. |
+- (void)makeAndPrepareSnapshotWindow; |
+ |
+// This method has several effects on |_primaryWindow|: |
+// - Saves current state. |
+// - Makes window transparent, with clear background. |
+// - Adds NSFullScreenWindowMask style mask. |
+// - Sets the size to the screen's size. |
+- (void)preparePrimaryWindowForAnimation; |
+ |
+// Applies the fullscreen animation to |_snapshotLayer|. |
+- (void)animateSnapshotWindowWithDuration:(CGFloat)duration; |
+ |
+// Applies the fullscreen animation to the root layer of |_primaryWindow|. |
+- (void)animatePrimaryWindowWithDuration:(CGFloat)duration; |
+ |
+// Override of CAAnimation delegate method. |
+- (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag; |
+ |
+// Returns the layer of the root view of |window|. |
+- (CALayer*)rootLayerOfWindow:(NSWindow*)window; |
+ |
+@end |
+ |
+@implementation BrowserWindowEnterFullscreenTransition |
+ |
+// -------------------------Public Methods---------------------------- |
+ |
+- (instancetype)initWithWindow:(NSWindow*)window { |
+ DCHECK(window); |
+ DCHECK([self rootLayerOfWindow:window]); |
+ if ((self = [super init])) { |
+ _primaryWindow.reset([window retain]); |
+ } |
+ return self; |
+} |
+ |
+- (void)dealloc { |
+ [_snapshotAnimation setDelegate:nil]; |
+ [_primaryWindowAnimation setDelegate:nil]; |
+ [super dealloc]; |
+} |
+ |
+- (NSArray*)customWindowsToEnterFullScreen { |
+ [self takeSnapshot]; |
+ [self makeAndPrepareSnapshotWindow]; |
+ [self preparePrimaryWindowForAnimation]; |
+ return @[ _primaryWindow.get(), _snapshotWindow.get() ]; |
+} |
+ |
+- (void)startCustomAnimationToEnterFullScreenWithDuration: |
+ (NSTimeInterval)duration { |
+ [self animateSnapshotWindowWithDuration:duration]; |
+ [self animatePrimaryWindowWithDuration:duration]; |
+} |
+ |
+- (BOOL)shouldWindowBeUnconstrained { |
+ return _changingPrimaryWindowSize; |
+} |
+ |
+// -------------------------Private Methods---------------------------- |
+ |
+- (void)takeSnapshot { |
+ base::ScopedCFTypeRef<CGImageRef> windowSnapshot(CGWindowListCreateImage( |
+ CGRectNull, kCGWindowListOptionIncludingWindow, |
+ [_primaryWindow windowNumber], kCGWindowImageBoundsIgnoreFraming)); |
+ _snapshotLayer.reset([[CALayer alloc] init]); |
+ [_snapshotLayer setFrame:NSRectToCGRect([_primaryWindow frame])]; |
+ [_snapshotLayer setContents:static_cast<id>(windowSnapshot.get())]; |
+ [_snapshotLayer setAnchorPoint:CGPointMake(0, 0)]; |
+ [_snapshotLayer setBackgroundColor:CGColorCreateGenericRGB(0, 0, 0, 0)]; |
+} |
+ |
+- (void)makeAndPrepareSnapshotWindow { |
+ _snapshotWindow.reset( |
+ [[NSWindow alloc] initWithContentRect:[[_primaryWindow screen] frame] |
+ styleMask:0 |
+ backing:NSBackingStoreBuffered |
+ defer:NO]); |
+ [[_snapshotWindow contentView] setWantsLayer:YES]; |
+ [_snapshotWindow setOpaque:NO]; |
+ [_snapshotWindow setBackgroundColor:[NSColor clearColor]]; |
+ [_snapshotWindow setAnimationBehavior:NSWindowAnimationBehaviorNone]; |
+ |
+ [_snapshotWindow orderFront:nil]; |
+ [[[_snapshotWindow contentView] layer] addSublayer:_snapshotLayer]; |
+ [_snapshotLayer setFrame:[_primaryWindow frame]]; |
+} |
+ |
+- (void)preparePrimaryWindowForAnimation { |
+ // Save initial state of |_primaryWindow|. |
+ _primaryWindowInitialFrame = [_primaryWindow frame]; |
+ _primaryWindowInitialBackgroundColor.reset( |
+ [[_primaryWindow backgroundColor] retain]); |
+ _primaryWindowInitialOpaque = [_primaryWindow isOpaque]; |
+ |
+ _primaryWindowFinalFrame = [[_primaryWindow screen] frame]; |
+ |
+ // Make |_primaryWindow| invisible. This must happen before the window is |
+ // resized. |
+ [_primaryWindow setOpaque:NO]; |
+ [_primaryWindow setBackgroundColor:[NSColor clearColor]]; |
+ CALayer* rootLayer = [self rootLayerOfWindow:_primaryWindow]; |
+ rootLayer.opacity = 0; |
+ |
+ // As soon as the style mask includes the flag NSFullScreenWindowMask, the |
+ // window is expected to receive fullscreen layout. This must be set before |
+ // the window is resized, as that causes a relayout. |
+ [_primaryWindow |
+ setStyleMask:[_primaryWindow styleMask] | NSFullScreenWindowMask]; |
+ |
+ // Resize |_primaryWindow|. |
+ _changingPrimaryWindowSize = YES; |
+ [_primaryWindow setFrame:_primaryWindowFinalFrame display:YES]; |
+ _changingPrimaryWindowSize = NO; |
+} |
+ |
+- (void)animateSnapshotWindowWithDuration:(CGFloat)duration { |
+ // Move the snapshot layer until it's bottom-left corner is at the |
+ // bottom-left corner of the screen. |
+ CABasicAnimation* positionAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"position"]; |
+ positionAnimation.toValue = [NSValue valueWithPoint:NSMakePoint(0, 0)]; |
Andre
2015/01/12 06:10:03
NSZeroPoint
erikchen
2015/01/12 19:18:58
Done.
|
+ positionAnimation.timingFunction = [CAMediaTimingFunction |
+ functionWithName:kTransformAnimationTimingFunction]; |
+ |
+ // Expand the bounds until it covers the screen. |
+ NSRect finalBounds = NSMakeRect(0, 0, NSWidth(_primaryWindowFinalFrame), |
+ NSHeight(_primaryWindowFinalFrame)); |
+ CABasicAnimation* boundsAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"bounds"]; |
+ boundsAnimation.toValue = [NSValue valueWithRect:finalBounds]; |
+ boundsAnimation.timingFunction = [CAMediaTimingFunction |
+ functionWithName:kTransformAnimationTimingFunction]; |
+ |
+ // Fade out the snapshot layer. |
+ CABasicAnimation* opacityAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"opacity"]; |
+ opacityAnimation.toValue = @(0); |
Andre
2015/01/12 06:10:03
@(0) is like -numberWithInt:, so this should be @(
erikchen
2015/01/12 19:18:58
Done.
|
+ opacityAnimation.timingFunction = |
+ [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; |
+ |
+ // Fill forwards, and don't remove the animation. When the animation |
+ // completes, the entire window will be removed. |
+ CAAnimationGroup* group = [CAAnimationGroup animation]; |
+ group.removedOnCompletion = NO; |
+ group.fillMode = kCAFillModeForwards; |
+ group.animations = @[ positionAnimation, boundsAnimation, opacityAnimation ]; |
+ group.duration = duration; |
+ [group setValue:kSnapshotWindowAnimationID forKey:kAnimationIDKey]; |
+ group.delegate = self; |
+ |
+ _snapshotAnimation.reset([group retain]); |
+ [_snapshotLayer addAnimation:group forKey:nil]; |
+} |
+ |
+- (void)animatePrimaryWindowWithDuration:(CGFloat)duration { |
+ // As soon as the window is downsized, the opacity should be set back to 1. |
+ // There are a couple of ways to do this. The easiest is to just have a dummy |
+ // animation as part of the same animation group. |
+ CABasicAnimation* opacityAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"opacity"]; |
+ opacityAnimation.fromValue = @(1); |
+ opacityAnimation.toValue = @(1); |
+ |
+ // Downsize the primary window, and the increase the size until it reaches it |
+ // expected end size. |
+ NSRect initialFrame = _primaryWindowInitialFrame; |
+ NSRect endFrame = _primaryWindowFinalFrame; |
+ CGFloat xScale = initialFrame.size.width / endFrame.size.width; |
+ CGFloat yScale = initialFrame.size.height / endFrame.size.height; |
Andre
2015/01/12 06:10:03
Use NSWidth and NSHeight for consistency.
erikchen
2015/01/12 19:18:58
Done.
Andre
2015/01/12 19:27:20
Forgot to change this?
erikchen
2015/01/12 19:31:57
Oops! Done.
|
+ CATransform3D initial = CATransform3DMakeScale(xScale, yScale, 1); |
+ CABasicAnimation* transformAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"transform"]; |
+ transformAnimation.fromValue = [NSValue valueWithCATransform3D:initial]; |
+ transformAnimation.toValue = |
+ [NSValue valueWithCATransform3D:CATransform3DIdentity]; |
+ transformAnimation.timingFunction = [CAMediaTimingFunction |
+ functionWithName:kTransformAnimationTimingFunction]; |
+ |
+ // Animate the primary window to its final position. |
+ CABasicAnimation* positionAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"position"]; |
+ CGPoint initialCenter = |
+ CGPointMake(CGRectGetMidX(initialFrame), CGRectGetMidY(initialFrame)); |
+ CGPoint finalCenter = |
+ CGPointMake(CGRectGetMidX(endFrame), CGRectGetMidY(endFrame)); |
Andre
2015/01/12 06:10:03
Mixing CG and NS works ok on 64-bits, but it would
erikchen
2015/01/12 19:18:58
You're right. Changed.
|
+ positionAnimation.fromValue = [NSValue valueWithPoint:initialCenter]; |
+ positionAnimation.toValue = [NSValue valueWithPoint:finalCenter]; |
+ positionAnimation.timingFunction = [CAMediaTimingFunction |
+ functionWithName:kTransformAnimationTimingFunction]; |
+ |
+ CAAnimationGroup* group = [CAAnimationGroup animation]; |
+ group.removedOnCompletion = NO; |
+ group.fillMode = kCAFillModeForwards; |
+ group.animations = |
+ @[ transformAnimation, opacityAnimation, positionAnimation ]; |
+ group.duration = duration; |
+ [group setValue:kPrimaryWindowAnimationID forKey:kAnimationIDKey]; |
+ group.delegate = self; |
+ |
+ _primaryWindowAnimation.reset([group retain]); |
+ CALayer* root = [self rootLayerOfWindow:_primaryWindow]; |
+ |
+ [root addAnimation:group forKey:kPrimaryWindowAnimationID]; |
+} |
+ |
+- (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag { |
+ NSString* animationID = [theAnimation valueForKey:kAnimationIDKey]; |
+ if ([animationID isEqual:kSnapshotWindowAnimationID]) { |
+ [_snapshotWindow orderOut:nil]; |
+ _snapshotWindow.reset(); |
+ _snapshotLayer.reset(); |
+ return; |
+ } |
+ |
+ if ([animationID isEqual:kPrimaryWindowAnimationID]) { |
+ [_primaryWindow setOpaque:YES]; |
+ [_primaryWindow setBackgroundColor:_primaryWindowInitialBackgroundColor]; |
+ CALayer* root = [self rootLayerOfWindow:_primaryWindow]; |
+ root.opacity = 1; |
+ [root removeAnimationForKey:kPrimaryWindowAnimationID]; |
+ } |
+} |
+ |
+- (CALayer*)rootLayerOfWindow:(NSWindow*)window { |
+ return [[[window contentView] superview] layer]; |
+} |
+ |
+@end |