Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(218)

Side by Side Diff: chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.mm

Issue 1276383004: Implemented fullscreen exit animation with AppKit (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Removed extra include Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.
4
5 #import "chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.h"
6
7 #include <QuartzCore/QuartzCore.h>
8
9 #include "base/mac/scoped_cftyperef.h"
10 #include "base/mac/scoped_nsobject.h"
11 #include "base/mac/sdk_forward_declarations.h"
12
13 namespace {
14
15 NSString* const kPrimaryWindowAnimationID = @"PrimaryWindowAnimationID";
16 NSString* const kSnapshotWindowAnimationID = @"SnapshotWindowAnimationID";
17 NSString* const kAnimationIDKey = @"AnimationIDKey";
18
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;
24 }
25
26 } // namespace
27
28 @interface BrowserWindowEnterFullscreenTransition () {
29 // The window which is undergoing the fullscreen transition.
30 base::scoped_nsobject<NSWindow> primaryWindow_;
31
32 // A layer that holds a snapshot of the original state of |primaryWindow_|.
33 base::scoped_nsobject<CALayer> snapshotLayer_;
34
35 // A temporary window that holds |snapshotLayer_|.
36 base::scoped_nsobject<NSWindow> snapshotWindow_;
37
38 // The frame of the |primaryWindow_| before the transition began.
39 NSRect primaryWindowInitialFrame_;
40
41 // The background color of |primaryWindow_| before the transition began.
42 base::scoped_nsobject<NSColor> primaryWindowInitialBackgroundColor_;
43
44 // Whether |primaryWindow_| was opaque before the transition began.
45 BOOL primaryWindowInitialOpaque_;
46
47 // Whether the instance is in the process of changing the size of
48 // |primaryWindow_|.
49 BOOL changingPrimaryWindowSize_;
50
51 // The frame that |primaryWindow_| is expected to have after the transition
52 // is finished.
53 NSRect primaryWindowFinalFrame_;
54 }
55
56 // Takes a snapshot of |primaryWindow_| and puts it in |snapshotLayer_|.
57 - (void)takeSnapshot;
58
59 // Creates |snapshotWindow_| and adds |snapshotLayer_| to it.
60 - (void)makeAndPrepareSnapshotWindow;
61
62 // This method has several effects on |primaryWindow_|:
63 // - Saves current state.
64 // - Makes window transparent, with clear background.
65 // - Adds NSFullScreenWindowMask style mask.
66 // - Sets the size to the screen's size.
67 - (void)preparePrimaryWindowForAnimation;
68
69 // Applies the fullscreen animation to |snapshotLayer_|.
70 - (void)animateSnapshotWindowWithDuration:(CGFloat)duration;
71
72 // Applies the fullscreen animation to the root layer of |primaryWindow_|.
73 - (void)animatePrimaryWindowWithDuration:(CGFloat)duration;
74
75 // Override of CAAnimation delegate method.
76 - (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag;
77
78 // Returns the layer of the root view of |window|.
79 - (CALayer*)rootLayerOfWindow:(NSWindow*)window;
80
81 @end
82
83 @implementation BrowserWindowEnterFullscreenTransition
84
85 // -------------------------Public Methods----------------------------
86
87 - (instancetype)initWithWindow:(NSWindow*)window {
88 DCHECK(window);
89 DCHECK([self rootLayerOfWindow:window]);
90 if ((self = [super init])) {
91 primaryWindow_.reset([window retain]);
92 }
93 return self;
94 }
95
96 - (NSArray*)customWindowsToEnterFullScreen {
97 [self takeSnapshot];
98 [self makeAndPrepareSnapshotWindow];
99 [self preparePrimaryWindowForAnimation];
100 return @[ primaryWindow_.get(), snapshotWindow_.get() ];
101 }
102
103 - (void)startCustomAnimationToEnterFullScreenWithDuration:
104 (NSTimeInterval)duration {
105 [self animateSnapshotWindowWithDuration:duration];
106 [self animatePrimaryWindowWithDuration:duration];
107 }
108
109 - (BOOL)shouldWindowBeUnconstrained {
110 return changingPrimaryWindowSize_;
111 }
112
113 // -------------------------Private Methods----------------------------
114
115 - (void)takeSnapshot {
116 base::ScopedCFTypeRef<CGImageRef> windowSnapshot(CGWindowListCreateImage(
117 CGRectNull, kCGWindowListOptionIncludingWindow,
118 [primaryWindow_ windowNumber], kCGWindowImageBoundsIgnoreFraming));
119 snapshotLayer_.reset([[CALayer alloc] init]);
120 [snapshotLayer_ setFrame:NSRectToCGRect([primaryWindow_ frame])];
121 [snapshotLayer_ setContents:static_cast<id>(windowSnapshot.get())];
122 [snapshotLayer_ setAnchorPoint:CGPointMake(0, 0)];
123 CGColorRef colorRef = CGColorCreateGenericRGB(0, 0, 0, 0);
124 [snapshotLayer_ setBackgroundColor:colorRef];
125 CGColorRelease(colorRef);
126 }
127
128 - (void)makeAndPrepareSnapshotWindow {
129 DCHECK(snapshotLayer_);
130
131 snapshotWindow_.reset(
132 [[NSWindow alloc] initWithContentRect:[[primaryWindow_ screen] frame]
133 styleMask:0
134 backing:NSBackingStoreBuffered
135 defer:NO]);
136 [[snapshotWindow_ contentView] setWantsLayer:YES];
137 [snapshotWindow_ setOpaque:NO];
138 [snapshotWindow_ setBackgroundColor:[NSColor clearColor]];
139 [snapshotWindow_ setAnimationBehavior:NSWindowAnimationBehaviorNone];
140
141 [snapshotWindow_ orderFront:nil];
142 [[[snapshotWindow_ contentView] layer] addSublayer:snapshotLayer_];
143
144 // Compute the frame of the snapshot layer such that the snapshot is
145 // positioned exactly on top of the original position of |primaryWindow_|.
146 NSRect snapshotLayerFrame =
147 [snapshotWindow_ convertRectFromScreen:[primaryWindow_ frame]];
148 [snapshotLayer_ setFrame:snapshotLayerFrame];
149 }
150
151 - (void)preparePrimaryWindowForAnimation {
152 // Save initial state of |primaryWindow_|.
153 primaryWindowInitialFrame_ = [primaryWindow_ frame];
154 primaryWindowInitialBackgroundColor_.reset(
155 [[primaryWindow_ backgroundColor] copy]);
156 primaryWindowInitialOpaque_ = [primaryWindow_ isOpaque];
157
158 primaryWindowFinalFrame_ = [[primaryWindow_ screen] frame];
159
160 // Make |primaryWindow_| invisible. This must happen before the window is
161 // resized, since resizing the window will call drawRect: and cause content
162 // to flash over the entire screen.
163 [primaryWindow_ setOpaque:NO];
164 [primaryWindow_ setBackgroundColor:[NSColor clearColor]];
165 CALayer* rootLayer = [self rootLayerOfWindow:primaryWindow_];
166 rootLayer.opacity = 0;
167
168 // As soon as the style mask includes the flag NSFullScreenWindowMask, the
169 // window is expected to receive fullscreen layout. This must be set before
170 // the window is resized, as that causes a relayout.
171 [primaryWindow_
172 setStyleMask:[primaryWindow_ styleMask] | NSFullScreenWindowMask];
173
174 // Resize |primaryWindow_|.
175 changingPrimaryWindowSize_ = YES;
176 [primaryWindow_ setFrame:primaryWindowFinalFrame_ display:YES];
177 changingPrimaryWindowSize_ = NO;
178 }
179
180 - (void)animateSnapshotWindowWithDuration:(CGFloat)duration {
181 // Move the snapshot layer until it's bottom-left corner is at the
182 // bottom-left corner of the screen.
183 CABasicAnimation* positionAnimation =
184 [CABasicAnimation animationWithKeyPath:@"position"];
185 positionAnimation.toValue = [NSValue valueWithPoint:NSZeroPoint];
186 positionAnimation.timingFunction = [CAMediaTimingFunction
187 functionWithName:TransformAnimationTimingFunction()];
188
189 // Expand the bounds until it covers the screen.
190 NSRect finalBounds = NSMakeRect(0, 0, NSWidth(primaryWindowFinalFrame_),
191 NSHeight(primaryWindowFinalFrame_));
192 CABasicAnimation* boundsAnimation =
193 [CABasicAnimation animationWithKeyPath:@"bounds"];
194 boundsAnimation.toValue = [NSValue valueWithRect:finalBounds];
195 boundsAnimation.timingFunction = [CAMediaTimingFunction
196 functionWithName:TransformAnimationTimingFunction()];
197
198 // Fade out the snapshot layer.
199 CABasicAnimation* opacityAnimation =
200 [CABasicAnimation animationWithKeyPath:@"opacity"];
201 opacityAnimation.toValue = @(0.0);
202 opacityAnimation.timingFunction =
203 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
204
205 // Fill forwards, and don't remove the animation. When the animation
206 // completes, the entire window will be removed.
207 CAAnimationGroup* group = [CAAnimationGroup animation];
208 group.removedOnCompletion = NO;
209 group.fillMode = kCAFillModeForwards;
210 group.animations = @[ positionAnimation, boundsAnimation, opacityAnimation ];
211 group.duration = duration;
212 [group setValue:kSnapshotWindowAnimationID forKey:kAnimationIDKey];
213 group.delegate = self;
214
215 [snapshotLayer_ addAnimation:group forKey:nil];
216 }
217
218 - (void)animatePrimaryWindowWithDuration:(CGFloat)duration {
219 // As soon as the window's root layer is scaled down, the opacity should be
220 // set back to 1. There are a couple of ways to do this. The easiest is to
221 // just have a dummy animation as part of the same animation group.
222 CABasicAnimation* opacityAnimation =
223 [CABasicAnimation animationWithKeyPath:@"opacity"];
224 opacityAnimation.fromValue = @(1.0);
225 opacityAnimation.toValue = @(1.0);
226
227 // The root layer's size should start scaled down to the initial size of
228 // |primaryWindow_|. The animation increases the size until the root layer
229 // fills the screen.
230 NSRect initialFrame = primaryWindowInitialFrame_;
231 NSRect endFrame = primaryWindowFinalFrame_;
232 CGFloat xScale = NSWidth(initialFrame) / NSWidth(endFrame);
233 CGFloat yScale = NSHeight(initialFrame) / NSHeight(endFrame);
234 CATransform3D initial = CATransform3DMakeScale(xScale, yScale, 1);
235 CABasicAnimation* transformAnimation =
236 [CABasicAnimation animationWithKeyPath:@"transform"];
237 transformAnimation.fromValue = [NSValue valueWithCATransform3D:initial];
238
239 CALayer* root = [self rootLayerOfWindow:primaryWindow_];
240
241 // Calculate the initial position of the root layer. This calculation is
242 // agnostic of the anchorPoint.
243 CGFloat layerStartPositionDeltaX = NSMidX(initialFrame) - NSMidX(endFrame);
244 CGFloat layerStartPositionDeltaY = NSMidY(initialFrame) - NSMidY(endFrame);
245 NSPoint layerStartPosition =
246 NSMakePoint(root.position.x + layerStartPositionDeltaX,
247 root.position.y + layerStartPositionDeltaY);
248
249 // Animate the primary window from its initial position.
250 CABasicAnimation* positionAnimation =
251 [CABasicAnimation animationWithKeyPath:@"position"];
252 positionAnimation.fromValue = [NSValue valueWithPoint:layerStartPosition];
253
254 CAAnimationGroup* group = [CAAnimationGroup animation];
255 group.removedOnCompletion = NO;
256 group.fillMode = kCAFillModeForwards;
257 group.animations =
258 @[ transformAnimation, opacityAnimation, positionAnimation ];
259 group.timingFunction = [CAMediaTimingFunction
260 functionWithName:TransformAnimationTimingFunction()];
261 group.duration = duration;
262 [group setValue:kPrimaryWindowAnimationID forKey:kAnimationIDKey];
263 group.delegate = self;
264
265 [root addAnimation:group forKey:kPrimaryWindowAnimationID];
266 }
267
268 - (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag {
269 NSString* animationID = [theAnimation valueForKey:kAnimationIDKey];
270 if ([animationID isEqual:kSnapshotWindowAnimationID]) {
271 [snapshotWindow_ orderOut:nil];
272 snapshotWindow_.reset();
273 snapshotLayer_.reset();
274 return;
275 }
276
277 if ([animationID isEqual:kPrimaryWindowAnimationID]) {
278 [primaryWindow_ setOpaque:YES];
279 [primaryWindow_ setBackgroundColor:primaryWindowInitialBackgroundColor_];
280 CALayer* root = [self rootLayerOfWindow:primaryWindow_];
281 root.opacity = 1;
282 [root removeAnimationForKey:kPrimaryWindowAnimationID];
283 }
284 }
285
286 - (CALayer*)rootLayerOfWindow:(NSWindow*)window {
287 return [[[window contentView] superview] layer];
288 }
289
290 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698