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

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: Comments from andresantoso, round 2. 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 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* 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;
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
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.
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
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())];
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
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]];
Robert Sesek 2015/01/12 22:05:27 DCHEC(_snapshotLayer) ?
erikchen 2015/01/13 02:51:27 Done.
151 }
152
153 - (void)preparePrimaryWindowForAnimation {
154 // Save initial state of |_primaryWindow|.
155 _primaryWindowInitialFrame = [_primaryWindow frame];
156 _primaryWindowInitialBackgroundColor.reset(
157 [[_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.
158 _primaryWindowInitialOpaque = [_primaryWindow isOpaque];
159
160 _primaryWindowFinalFrame = [[_primaryWindow screen] frame];
161
162 // Make |_primaryWindow| invisible. This must happen before the window is
163 // 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.
164 [_primaryWindow setOpaque:NO];
165 [_primaryWindow setBackgroundColor:[NSColor clearColor]];
166 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
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:NSZeroPoint];
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.0);
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];
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
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.
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
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.0);
227 opacityAnimation.toValue = @(1.0);
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 = NSWidth(initialFrame) / NSWidth(endFrame);
234 CGFloat yScale = NSHeight(initialFrame) / NSHeight(endFrame);
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
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.
242 functionWithName:kTransformAnimationTimingFunction];
243
244 // Animate the primary window to its final position.
245 CABasicAnimation* positionAnimation =
246 [CABasicAnimation animationWithKeyPath:@"position"];
247 NSPoint initialCenter =
248 NSMakePoint(NSMidX(initialFrame), NSMidY(initialFrame));
249 NSPoint finalCenter = NSMakePoint(NSMidX(endFrame), NSMidY(endFrame));
250 positionAnimation.fromValue = [NSValue valueWithPoint:initialCenter];
251 positionAnimation.toValue = [NSValue valueWithPoint:finalCenter];
252 positionAnimation.timingFunction = [CAMediaTimingFunction
253 functionWithName:kTransformAnimationTimingFunction];
254
255 CAAnimationGroup* group = [CAAnimationGroup animation];
256 group.removedOnCompletion = NO;
257 group.fillMode = kCAFillModeForwards;
258 group.animations =
259 @[ transformAnimation, opacityAnimation, positionAnimation ];
260 group.duration = duration;
261 [group setValue:kPrimaryWindowAnimationID forKey:kAnimationIDKey];
262 group.delegate = self;
263
264 _primaryWindowAnimation.reset([group retain]);
265 CALayer* root = [self rootLayerOfWindow:_primaryWindow];
266
267 [root addAnimation:group forKey:kPrimaryWindowAnimationID];
268 }
269
270 - (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag {
271 NSString* animationID = [theAnimation valueForKey:kAnimationIDKey];
272 if ([animationID isEqual:kSnapshotWindowAnimationID]) {
273 [_snapshotWindow orderOut:nil];
274 _snapshotWindow.reset();
275 _snapshotLayer.reset();
276 return;
277 }
278
279 if ([animationID isEqual:kPrimaryWindowAnimationID]) {
280 [_primaryWindow setOpaque:YES];
281 [_primaryWindow setBackgroundColor:_primaryWindowInitialBackgroundColor];
282 CALayer* root = [self rootLayerOfWindow:_primaryWindow];
283 root.opacity = 1;
284 [root removeAnimationForKey:kPrimaryWindowAnimationID];
285 }
286 }
287
288 - (CALayer*)rootLayerOfWindow:(NSWindow*)window {
289 return [[[window contentView] superview] layer];
290 }
291
292 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698