Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / browser_window_enter_fullscreen_transition.mm
blob360c81a9791223641a605b4afe318b71f228a9e1
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.h"
7 #include <QuartzCore/QuartzCore.h>
9 #include "base/mac/scoped_cftyperef.h"
10 #include "base/mac/scoped_nsobject.h"
11 #include "base/mac/sdk_forward_declarations.h"
13 namespace {
15 NSString* const kPrimaryWindowAnimationID = @"PrimaryWindowAnimationID";
16 NSString* const kSnapshotWindowAnimationID = @"SnapshotWindowAnimationID";
17 NSString* const kAnimationIDKey = @"AnimationIDKey";
19 // This class has two simultaneous animations to resize and reposition layers.
20 // These animations must use the same timing function, otherwise there will be
21 // visual discordance.
22 NSString* TransformAnimationTimingFunction() {
23   return kCAMediaTimingFunctionEaseInEaseOut;
26 }  // namespace
28 @interface BrowserWindowEnterFullscreenTransition () {
29   // The window which is undergoing the fullscreen transition.
30   base::scoped_nsobject<NSWindow> primaryWindow_;
32   // A layer that holds a snapshot of the original state of |primaryWindow_|.
33   base::scoped_nsobject<CALayer> snapshotLayer_;
35   // A temporary window that holds |snapshotLayer_|.
36   base::scoped_nsobject<NSWindow> snapshotWindow_;
38   // The animation applied to |snapshotLayer_|.
39   base::scoped_nsobject<CAAnimationGroup> snapshotAnimation_;
41   // The animation applied to the root layer of |primaryWindow_|.
42   base::scoped_nsobject<CAAnimationGroup> primaryWindowAnimation_;
44   // The frame of the |primaryWindow_| before the transition began.
45   NSRect primaryWindowInitialFrame_;
47   // The background color of |primaryWindow_| before the transition began.
48   base::scoped_nsobject<NSColor> primaryWindowInitialBackgroundColor_;
50   // Whether |primaryWindow_| was opaque before the transition began.
51   BOOL primaryWindowInitialOpaque_;
53   // Whether the instance is in the process of changing the size of
54   // |primaryWindow_|.
55   BOOL changingPrimaryWindowSize_;
57   // The frame that |primaryWindow_| is expected to have after the transition
58   // is finished.
59   NSRect primaryWindowFinalFrame_;
62 // Takes a snapshot of |primaryWindow_| and puts it in |snapshotLayer_|.
63 - (void)takeSnapshot;
65 // Creates |snapshotWindow_| and adds |snapshotLayer_| to it.
66 - (void)makeAndPrepareSnapshotWindow;
68 // This method has several effects on |primaryWindow_|:
69 //  - Saves current state.
70 //  - Makes window transparent, with clear background.
71 //  - Adds NSFullScreenWindowMask style mask.
72 //  - Sets the size to the screen's size.
73 - (void)preparePrimaryWindowForAnimation;
75 // Applies the fullscreen animation to |snapshotLayer_|.
76 - (void)animateSnapshotWindowWithDuration:(CGFloat)duration;
78 // Applies the fullscreen animation to the root layer of |primaryWindow_|.
79 - (void)animatePrimaryWindowWithDuration:(CGFloat)duration;
81 // Override of CAAnimation delegate method.
82 - (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag;
84 // Returns the layer of the root view of |window|.
85 - (CALayer*)rootLayerOfWindow:(NSWindow*)window;
87 @end
89 @implementation BrowserWindowEnterFullscreenTransition
91 // -------------------------Public Methods----------------------------
93 - (instancetype)initWithWindow:(NSWindow*)window {
94   DCHECK(window);
95   DCHECK([self rootLayerOfWindow:window]);
96   if ((self = [super init])) {
97     primaryWindow_.reset([window retain]);
98   }
99   return self;
102 - (void)dealloc {
103   [snapshotAnimation_ setDelegate:nil];
104   [primaryWindowAnimation_ setDelegate:nil];
105   [super dealloc];
108 - (NSArray*)customWindowsToEnterFullScreen {
109   [self takeSnapshot];
110   [self makeAndPrepareSnapshotWindow];
111   [self preparePrimaryWindowForAnimation];
112   return @[ primaryWindow_.get(), snapshotWindow_.get() ];
115 - (void)startCustomAnimationToEnterFullScreenWithDuration:
116     (NSTimeInterval)duration {
117   [self animateSnapshotWindowWithDuration:duration];
118   [self animatePrimaryWindowWithDuration:duration];
121 - (BOOL)shouldWindowBeUnconstrained {
122   return changingPrimaryWindowSize_;
125 // -------------------------Private Methods----------------------------
127 - (void)takeSnapshot {
128   base::ScopedCFTypeRef<CGImageRef> windowSnapshot(CGWindowListCreateImage(
129       CGRectNull, kCGWindowListOptionIncludingWindow,
130       [primaryWindow_ windowNumber], kCGWindowImageBoundsIgnoreFraming));
131   snapshotLayer_.reset([[CALayer alloc] init]);
132   [snapshotLayer_ setFrame:NSRectToCGRect([primaryWindow_ frame])];
133   [snapshotLayer_ setContents:static_cast<id>(windowSnapshot.get())];
134   [snapshotLayer_ setAnchorPoint:CGPointMake(0, 0)];
135   [snapshotLayer_ setBackgroundColor:CGColorCreateGenericRGB(0, 0, 0, 0)];
138 - (void)makeAndPrepareSnapshotWindow {
139   DCHECK(snapshotLayer_);
141   snapshotWindow_.reset(
142       [[NSWindow alloc] initWithContentRect:[[primaryWindow_ screen] frame]
143                                   styleMask:0
144                                     backing:NSBackingStoreBuffered
145                                       defer:NO]);
146   [[snapshotWindow_ contentView] setWantsLayer:YES];
147   [snapshotWindow_ setOpaque:NO];
148   [snapshotWindow_ setBackgroundColor:[NSColor clearColor]];
149   [snapshotWindow_ setAnimationBehavior:NSWindowAnimationBehaviorNone];
151   [snapshotWindow_ orderFront:nil];
152   [[[snapshotWindow_ contentView] layer] addSublayer:snapshotLayer_];
154   // Compute the frame of the snapshot layer such that the snapshot is
155   // positioned exactly on top of the original position of |primaryWindow_|.
156   NSRect snapshotLayerFrame =
157       [snapshotWindow_ convertRectFromScreen:[primaryWindow_ frame]];
158   [snapshotLayer_ setFrame:snapshotLayerFrame];
161 - (void)preparePrimaryWindowForAnimation {
162   // Save initial state of |primaryWindow_|.
163   primaryWindowInitialFrame_ = [primaryWindow_ frame];
164   primaryWindowInitialBackgroundColor_.reset(
165       [[primaryWindow_ backgroundColor] copy]);
166   primaryWindowInitialOpaque_ = [primaryWindow_ isOpaque];
168   primaryWindowFinalFrame_ = [[primaryWindow_ screen] frame];
170   // Make |primaryWindow_| invisible. This must happen before the window is
171   // resized, since resizing the window will call drawRect: and cause content
172   // to flash over the entire screen.
173   [primaryWindow_ setOpaque:NO];
174   [primaryWindow_ setBackgroundColor:[NSColor clearColor]];
175   CALayer* rootLayer = [self rootLayerOfWindow:primaryWindow_];
176   rootLayer.opacity = 0;
178   // As soon as the style mask includes the flag NSFullScreenWindowMask, the
179   // window is expected to receive fullscreen layout. This must be set before
180   // the window is resized, as that causes a relayout.
181   [primaryWindow_
182       setStyleMask:[primaryWindow_ styleMask] | NSFullScreenWindowMask];
184   // Resize |primaryWindow_|.
185   changingPrimaryWindowSize_ = YES;
186   [primaryWindow_ setFrame:primaryWindowFinalFrame_ display:YES];
187   changingPrimaryWindowSize_ = NO;
190 - (void)animateSnapshotWindowWithDuration:(CGFloat)duration {
191   // Move the snapshot layer until it's bottom-left corner is at the
192   // bottom-left corner of the screen.
193   CABasicAnimation* positionAnimation =
194       [CABasicAnimation animationWithKeyPath:@"position"];
195   positionAnimation.toValue = [NSValue valueWithPoint:NSZeroPoint];
196   positionAnimation.timingFunction = [CAMediaTimingFunction
197       functionWithName:TransformAnimationTimingFunction()];
199   // Expand the bounds until it covers the screen.
200   NSRect finalBounds = NSMakeRect(0, 0, NSWidth(primaryWindowFinalFrame_),
201                                   NSHeight(primaryWindowFinalFrame_));
202   CABasicAnimation* boundsAnimation =
203       [CABasicAnimation animationWithKeyPath:@"bounds"];
204   boundsAnimation.toValue = [NSValue valueWithRect:finalBounds];
205   boundsAnimation.timingFunction = [CAMediaTimingFunction
206       functionWithName:TransformAnimationTimingFunction()];
208   // Fade out the snapshot layer.
209   CABasicAnimation* opacityAnimation =
210       [CABasicAnimation animationWithKeyPath:@"opacity"];
211   opacityAnimation.toValue = @(0.0);
212   opacityAnimation.timingFunction =
213       [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
215   // Fill forwards, and don't remove the animation. When the animation
216   // completes, the entire window will be removed.
217   CAAnimationGroup* group = [CAAnimationGroup animation];
218   group.removedOnCompletion = NO;
219   group.fillMode = kCAFillModeForwards;
220   group.animations = @[ positionAnimation, boundsAnimation, opacityAnimation ];
221   group.duration = duration;
222   [group setValue:kSnapshotWindowAnimationID forKey:kAnimationIDKey];
223   group.delegate = self;
225   snapshotAnimation_.reset([group retain]);
226   [snapshotLayer_ addAnimation:group forKey:nil];
229 - (void)animatePrimaryWindowWithDuration:(CGFloat)duration {
230   // As soon as the window's root layer is scaled down, the opacity should be
231   // set back to 1. There are a couple of ways to do this. The easiest is to
232   // just have a dummy animation as part of the same animation group.
233   CABasicAnimation* opacityAnimation =
234       [CABasicAnimation animationWithKeyPath:@"opacity"];
235   opacityAnimation.fromValue = @(1.0);
236   opacityAnimation.toValue = @(1.0);
238   // The root layer's size should start scaled down to the initial size of
239   // |primaryWindow_|. The animation increases the size until the root layer
240   // fills the screen.
241   NSRect initialFrame = primaryWindowInitialFrame_;
242   NSRect endFrame = primaryWindowFinalFrame_;
243   CGFloat xScale = NSWidth(initialFrame) / NSWidth(endFrame);
244   CGFloat yScale = NSHeight(initialFrame) / NSHeight(endFrame);
245   CATransform3D initial = CATransform3DMakeScale(xScale, yScale, 1);
246   CABasicAnimation* transformAnimation =
247       [CABasicAnimation animationWithKeyPath:@"transform"];
248   transformAnimation.fromValue = [NSValue valueWithCATransform3D:initial];
250   CALayer* root = [self rootLayerOfWindow:primaryWindow_];
252   // Calculate the initial position of the root layer. This calculation is
253   // agnostic of the anchorPoint.
254   CGFloat layerStartPositionDeltaX = NSMidX(initialFrame) - NSMidX(endFrame);
255   CGFloat layerStartPositionDeltaY = NSMidY(initialFrame) - NSMidY(endFrame);
256   NSPoint layerStartPosition =
257       NSMakePoint(root.position.x + layerStartPositionDeltaX,
258                   root.position.y + layerStartPositionDeltaY);
260   // Animate the primary window from its initial position.
261   CABasicAnimation* positionAnimation =
262       [CABasicAnimation animationWithKeyPath:@"position"];
263   positionAnimation.fromValue = [NSValue valueWithPoint:layerStartPosition];
265   CAAnimationGroup* group = [CAAnimationGroup animation];
266   group.removedOnCompletion = NO;
267   group.fillMode = kCAFillModeForwards;
268   group.animations =
269       @[ transformAnimation, opacityAnimation, positionAnimation ];
270   group.timingFunction = [CAMediaTimingFunction
271       functionWithName:TransformAnimationTimingFunction()];
272   group.duration = duration;
273   [group setValue:kPrimaryWindowAnimationID forKey:kAnimationIDKey];
274   group.delegate = self;
276   primaryWindowAnimation_.reset([group retain]);
278   [root addAnimation:group forKey:kPrimaryWindowAnimationID];
281 - (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag {
282   NSString* animationID = [theAnimation valueForKey:kAnimationIDKey];
283   if ([animationID isEqual:kSnapshotWindowAnimationID]) {
284     [snapshotWindow_ orderOut:nil];
285     snapshotWindow_.reset();
286     snapshotLayer_.reset();
287     return;
288   }
290   if ([animationID isEqual:kPrimaryWindowAnimationID]) {
291     [primaryWindow_ setOpaque:YES];
292     [primaryWindow_ setBackgroundColor:primaryWindowInitialBackgroundColor_];
293     CALayer* root = [self rootLayerOfWindow:primaryWindow_];
294     root.opacity = 1;
295     [root removeAnimationForKey:kPrimaryWindowAnimationID];
296   }
299 - (CALayer*)rootLayerOfWindow:(NSWindow*)window {
300   return [[[window contentView] superview] layer];
303 @end