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

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

Issue 846443004: mac: Implement custom AppKit Enter Fullscreen transition. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 11 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 2014 The Chromium Authors. All rights reserved.
Andre 2015/01/12 06:10:03 2015
erikchen 2015/01/12 19:18:58 Done.
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* const kTransformAnimationTimingFunction =
23 kCAMediaTimingFunctionEaseInEaseOut;
24
25 } // namespace
26
27 @interface BrowserWindowEnterFullscreenTransition () {
28 // The window which is undergoing the fullscreen transition.
29 base::scoped_nsobject<NSWindow> _primaryWindow;
30
31 // A layer that holds a snapshot of the original state of |_primaryWindow|.
32 base::scoped_nsobject<CALayer> _snapshotLayer;
33
34 // A temporary window that holds |_snapshotLayer|.
35 base::scoped_nsobject<NSWindow> _snapshotWindow;
36
37 // The animation applied to |_snapshotLayer|.
38 base::scoped_nsobject<CAAnimationGroup> _snapshotAnimation;
39
40 // The animation applied to the root layer of |_primaryWindow|.
41 base::scoped_nsobject<CAAnimationGroup> _primaryWindowAnimation;
42
43 // The frame of the |_primaryWindow| before the transition began.
44 NSRect _primaryWindowInitialFrame;
45
46 // The background color of |_primaryWindow| before the transition began.
47 base::scoped_nsobject<NSColor> _primaryWindowInitialBackgroundColor;
48
49 // Whether |_primaryWindow| was opaque before the transition began.
50 BOOL _primaryWindowInitialOpaque;
51
52 // Whether the instance is in the process of changing the size of
53 // |_primaryWindow|.
54 BOOL _changingPrimaryWindowSize;
55
56 // The frame that |_primaryWindow| is expected to have after the transition
57 // is finished.
58 NSRect _primaryWindowFinalFrame;
59 }
60
61 // Takes a snapshot of |_primaryWindow| and puts it in |_snapshotLayer|.
62 - (void)takeSnapshot;
63
64 // Creates |_snapshotWindow| and adds |_snapshotLayer| to it.
65 - (void)makeAndPrepareSnapshotWindow;
66
67 // This method has several effects on |_primaryWindow|:
68 // - Saves current state.
69 // - Makes window transparent, with clear background.
70 // - Adds NSFullScreenWindowMask style mask.
71 // - Sets the size to the screen's size.
72 - (void)preparePrimaryWindowForAnimation;
73
74 // Applies the fullscreen animation to |_snapshotLayer|.
75 - (void)animateSnapshotWindowWithDuration:(CGFloat)duration;
76
77 // Applies the fullscreen animation to the root layer of |_primaryWindow|.
78 - (void)animatePrimaryWindowWithDuration:(CGFloat)duration;
79
80 // Override of CAAnimation delegate method.
81 - (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag;
82
83 // Returns the layer of the root view of |window|.
84 - (CALayer*)rootLayerOfWindow:(NSWindow*)window;
85
86 @end
87
88 @implementation BrowserWindowEnterFullscreenTransition
89
90 // -------------------------Public Methods----------------------------
91
92 - (instancetype)initWithWindow:(NSWindow*)window {
93 DCHECK(window);
94 DCHECK([self rootLayerOfWindow:window]);
95 if ((self = [super init])) {
96 _primaryWindow.reset([window retain]);
97 }
98 return self;
99 }
100
101 - (void)dealloc {
102 [_snapshotAnimation setDelegate:nil];
103 [_primaryWindowAnimation setDelegate:nil];
104 [super dealloc];
105 }
106
107 - (NSArray*)customWindowsToEnterFullScreen {
108 [self takeSnapshot];
109 [self makeAndPrepareSnapshotWindow];
110 [self preparePrimaryWindowForAnimation];
111 return @[ _primaryWindow.get(), _snapshotWindow.get() ];
112 }
113
114 - (void)startCustomAnimationToEnterFullScreenWithDuration:
115 (NSTimeInterval)duration {
116 [self animateSnapshotWindowWithDuration:duration];
117 [self animatePrimaryWindowWithDuration:duration];
118 }
119
120 - (BOOL)shouldWindowBeUnconstrained {
121 return _changingPrimaryWindowSize;
122 }
123
124 // -------------------------Private Methods----------------------------
125
126 - (void)takeSnapshot {
127 base::ScopedCFTypeRef<CGImageRef> windowSnapshot(CGWindowListCreateImage(
128 CGRectNull, kCGWindowListOptionIncludingWindow,
129 [_primaryWindow windowNumber], kCGWindowImageBoundsIgnoreFraming));
130 _snapshotLayer.reset([[CALayer alloc] init]);
131 [_snapshotLayer setFrame:NSRectToCGRect([_primaryWindow frame])];
132 [_snapshotLayer setContents:static_cast<id>(windowSnapshot.get())];
133 [_snapshotLayer setAnchorPoint:CGPointMake(0, 0)];
134 [_snapshotLayer setBackgroundColor:CGColorCreateGenericRGB(0, 0, 0, 0)];
135 }
136
137 - (void)makeAndPrepareSnapshotWindow {
138 _snapshotWindow.reset(
139 [[NSWindow alloc] initWithContentRect:[[_primaryWindow screen] frame]
140 styleMask:0
141 backing:NSBackingStoreBuffered
142 defer:NO]);
143 [[_snapshotWindow contentView] setWantsLayer:YES];
144 [_snapshotWindow setOpaque:NO];
145 [_snapshotWindow setBackgroundColor:[NSColor clearColor]];
146 [_snapshotWindow setAnimationBehavior:NSWindowAnimationBehaviorNone];
147
148 [_snapshotWindow orderFront:nil];
149 [[[_snapshotWindow contentView] layer] addSublayer:_snapshotLayer];
150 [_snapshotLayer setFrame:[_primaryWindow frame]];
151 }
152
153 - (void)preparePrimaryWindowForAnimation {
154 // Save initial state of |_primaryWindow|.
155 _primaryWindowInitialFrame = [_primaryWindow frame];
156 _primaryWindowInitialBackgroundColor.reset(
157 [[_primaryWindow backgroundColor] retain]);
158 _primaryWindowInitialOpaque = [_primaryWindow isOpaque];
159
160 _primaryWindowFinalFrame = [[_primaryWindow screen] frame];
161
162 // Make |_primaryWindow| invisible. This must happen before the window is
163 // resized.
164 [_primaryWindow setOpaque:NO];
165 [_primaryWindow setBackgroundColor:[NSColor clearColor]];
166 CALayer* rootLayer = [self rootLayerOfWindow:_primaryWindow];
167 rootLayer.opacity = 0;
168
169 // As soon as the style mask includes the flag NSFullScreenWindowMask, the
170 // window is expected to receive fullscreen layout. This must be set before
171 // the window is resized, as that causes a relayout.
172 [_primaryWindow
173 setStyleMask:[_primaryWindow styleMask] | NSFullScreenWindowMask];
174
175 // Resize |_primaryWindow|.
176 _changingPrimaryWindowSize = YES;
177 [_primaryWindow setFrame:_primaryWindowFinalFrame display:YES];
178 _changingPrimaryWindowSize = NO;
179 }
180
181 - (void)animateSnapshotWindowWithDuration:(CGFloat)duration {
182 // Move the snapshot layer until it's bottom-left corner is at the
183 // bottom-left corner of the screen.
184 CABasicAnimation* positionAnimation =
185 [CABasicAnimation animationWithKeyPath:@"position"];
186 positionAnimation.toValue = [NSValue valueWithPoint:NSMakePoint(0, 0)];
Andre 2015/01/12 06:10:03 NSZeroPoint
erikchen 2015/01/12 19:18:58 Done.
187 positionAnimation.timingFunction = [CAMediaTimingFunction
188 functionWithName:kTransformAnimationTimingFunction];
189
190 // Expand the bounds until it covers the screen.
191 NSRect finalBounds = NSMakeRect(0, 0, NSWidth(_primaryWindowFinalFrame),
192 NSHeight(_primaryWindowFinalFrame));
193 CABasicAnimation* boundsAnimation =
194 [CABasicAnimation animationWithKeyPath:@"bounds"];
195 boundsAnimation.toValue = [NSValue valueWithRect:finalBounds];
196 boundsAnimation.timingFunction = [CAMediaTimingFunction
197 functionWithName:kTransformAnimationTimingFunction];
198
199 // Fade out the snapshot layer.
200 CABasicAnimation* opacityAnimation =
201 [CABasicAnimation animationWithKeyPath:@"opacity"];
202 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.
203 opacityAnimation.timingFunction =
204 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
205
206 // Fill forwards, and don't remove the animation. When the animation
207 // completes, the entire window will be removed.
208 CAAnimationGroup* group = [CAAnimationGroup animation];
209 group.removedOnCompletion = NO;
210 group.fillMode = kCAFillModeForwards;
211 group.animations = @[ positionAnimation, boundsAnimation, opacityAnimation ];
212 group.duration = duration;
213 [group setValue:kSnapshotWindowAnimationID forKey:kAnimationIDKey];
214 group.delegate = self;
215
216 _snapshotAnimation.reset([group retain]);
217 [_snapshotLayer addAnimation:group forKey:nil];
218 }
219
220 - (void)animatePrimaryWindowWithDuration:(CGFloat)duration {
221 // As soon as the window is downsized, the opacity should be set back to 1.
222 // There are a couple of ways to do this. The easiest is to just have a dummy
223 // animation as part of the same animation group.
224 CABasicAnimation* opacityAnimation =
225 [CABasicAnimation animationWithKeyPath:@"opacity"];
226 opacityAnimation.fromValue = @(1);
227 opacityAnimation.toValue = @(1);
228
229 // Downsize the primary window, and the increase the size until it reaches it
230 // expected end size.
231 NSRect initialFrame = _primaryWindowInitialFrame;
232 NSRect endFrame = _primaryWindowFinalFrame;
233 CGFloat xScale = initialFrame.size.width / endFrame.size.width;
234 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.
235 CATransform3D initial = CATransform3DMakeScale(xScale, yScale, 1);
236 CABasicAnimation* transformAnimation =
237 [CABasicAnimation animationWithKeyPath:@"transform"];
238 transformAnimation.fromValue = [NSValue valueWithCATransform3D:initial];
239 transformAnimation.toValue =
240 [NSValue valueWithCATransform3D:CATransform3DIdentity];
241 transformAnimation.timingFunction = [CAMediaTimingFunction
242 functionWithName:kTransformAnimationTimingFunction];
243
244 // Animate the primary window to its final position.
245 CABasicAnimation* positionAnimation =
246 [CABasicAnimation animationWithKeyPath:@"position"];
247 CGPoint initialCenter =
248 CGPointMake(CGRectGetMidX(initialFrame), CGRectGetMidY(initialFrame));
249 CGPoint finalCenter =
250 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.
251 positionAnimation.fromValue = [NSValue valueWithPoint:initialCenter];
252 positionAnimation.toValue = [NSValue valueWithPoint:finalCenter];
253 positionAnimation.timingFunction = [CAMediaTimingFunction
254 functionWithName:kTransformAnimationTimingFunction];
255
256 CAAnimationGroup* group = [CAAnimationGroup animation];
257 group.removedOnCompletion = NO;
258 group.fillMode = kCAFillModeForwards;
259 group.animations =
260 @[ transformAnimation, opacityAnimation, positionAnimation ];
261 group.duration = duration;
262 [group setValue:kPrimaryWindowAnimationID forKey:kAnimationIDKey];
263 group.delegate = self;
264
265 _primaryWindowAnimation.reset([group retain]);
266 CALayer* root = [self rootLayerOfWindow:_primaryWindow];
267
268 [root addAnimation:group forKey:kPrimaryWindowAnimationID];
269 }
270
271 - (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag {
272 NSString* animationID = [theAnimation valueForKey:kAnimationIDKey];
273 if ([animationID isEqual:kSnapshotWindowAnimationID]) {
274 [_snapshotWindow orderOut:nil];
275 _snapshotWindow.reset();
276 _snapshotLayer.reset();
277 return;
278 }
279
280 if ([animationID isEqual:kPrimaryWindowAnimationID]) {
281 [_primaryWindow setOpaque:YES];
282 [_primaryWindow setBackgroundColor:_primaryWindowInitialBackgroundColor];
283 CALayer* root = [self rootLayerOfWindow:_primaryWindow];
284 root.opacity = 1;
285 [root removeAnimationForKey:kPrimaryWindowAnimationID];
286 }
287 }
288
289 - (CALayer*)rootLayerOfWindow:(NSWindow*)window {
290 return [[[window contentView] superview] layer];
291 }
292
293 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698