Chromium Code Reviews| 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..1b214d3e6b01cce919099a1c9adea9e75aa669eb |
| --- /dev/null |
| +++ b/chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.mm |
| @@ -0,0 +1,292 @@ |
| +// Copyright 2015 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. |
| + |
| +#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; |
|
Robert Sesek
2015/01/12 22:05:27
No other code in Chromium uses this style, and swi
erikchen
2015/01/13 02:51:28
I have changed all the ivars to use trailing under
|
| + |
| + // 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. |
|
Robert Sesek
2015/01/12 22:05:27
Is this ever variable for Chromium windows? Or is
erikchen
2015/01/13 02:51:27
This is never variable for BrowserWindow. This iva
|
| + 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())]; |
|
Robert Sesek
2015/01/12 22:05:27
Why does this need a cast?
erikchen
2015/01/13 02:51:28
The property |contents| has type id. CGImageRef is
|
| + [_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]]; |
|
Robert Sesek
2015/01/12 22:05:27
DCHEC(_snapshotLayer) ?
erikchen
2015/01/13 02:51:27
Done.
|
| +} |
| + |
| +- (void)preparePrimaryWindowForAnimation { |
| + // Save initial state of |_primaryWindow|. |
| + _primaryWindowInitialFrame = [_primaryWindow frame]; |
| + _primaryWindowInitialBackgroundColor.reset( |
| + [[_primaryWindow backgroundColor] retain]); |
|
Robert Sesek
2015/01/12 22:05:27
copy may be better here
erikchen
2015/01/13 02:51:28
Could you please explain the reasoning behind your
Robert Sesek
2015/01/15 01:36:54
NSColor is a value object that implements NSCopyin
erikchen
2015/01/15 18:50:16
Ah, you're right. Done.
|
| + _primaryWindowInitialOpaque = [_primaryWindow isOpaque]; |
| + |
| + _primaryWindowFinalFrame = [[_primaryWindow screen] frame]; |
| + |
| + // Make |_primaryWindow| invisible. This must happen before the window is |
| + // resized. |
|
Robert Sesek
2015/01/12 22:05:27
Why? Prevent -drawRect: calls?
erikchen
2015/01/13 02:51:28
That is correct. I've updated the documentation.
|
| + [_primaryWindow setOpaque:NO]; |
| + [_primaryWindow setBackgroundColor:[NSColor clearColor]]; |
| + CALayer* rootLayer = [self rootLayerOfWindow:_primaryWindow]; |
|
erikchen
2015/01/12 19:50:45
As andre pointed out, we can save a couple of line
erikchen
2015/01/13 02:51:28
Unfortunately, setting the window's alpha to zero
|
| + 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:NSZeroPoint]; |
| + 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.0); |
| + 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]; |
|
Robert Sesek
2015/01/12 22:05:27
What are these animation IDs used for? Can't you j
erikchen
2015/01/13 02:51:28
The CAAnimation* passed to -animationDidEnd: is ne
|
| + 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. |
|
Robert Sesek
2015/01/12 22:05:27
"Downsize" ?
erikchen
2015/01/13 02:51:28
I don't understand your question, so I've addresse
|
| + // 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.0); |
| + opacityAnimation.toValue = @(1.0); |
| + |
| + // Downsize the primary window, and the increase the size until it reaches it |
| + // expected end size. |
| + NSRect initialFrame = _primaryWindowInitialFrame; |
| + NSRect endFrame = _primaryWindowFinalFrame; |
| + CGFloat xScale = NSWidth(initialFrame) / NSWidth(endFrame); |
| + CGFloat yScale = NSHeight(initialFrame) / NSHeight(endFrame); |
| + CATransform3D initial = CATransform3DMakeScale(xScale, yScale, 1); |
| + CABasicAnimation* transformAnimation = |
| + [CABasicAnimation animationWithKeyPath:@"transform"]; |
| + transformAnimation.fromValue = [NSValue valueWithCATransform3D:initial]; |
| + transformAnimation.toValue = |
| + [NSValue valueWithCATransform3D:CATransform3DIdentity]; |
| + transformAnimation.timingFunction = [CAMediaTimingFunction |
|
Robert Sesek
2015/01/12 22:05:27
Can you not set the timing function on the group?
erikchen
2015/01/13 02:51:28
I can. I have done so.
|
| + functionWithName:kTransformAnimationTimingFunction]; |
| + |
| + // Animate the primary window to its final position. |
| + CABasicAnimation* positionAnimation = |
| + [CABasicAnimation animationWithKeyPath:@"position"]; |
| + NSPoint initialCenter = |
| + NSMakePoint(NSMidX(initialFrame), NSMidY(initialFrame)); |
| + NSPoint finalCenter = NSMakePoint(NSMidX(endFrame), NSMidY(endFrame)); |
| + 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 |