OLD | NEW |
| (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_; | |
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 DCHECK(snapshotLayer_); | |
139 | |
140 snapshotWindow_.reset( | |
141 [[NSWindow alloc] initWithContentRect:[[primaryWindow_ screen] frame] | |
142 styleMask:0 | |
143 backing:NSBackingStoreBuffered | |
144 defer:NO]); | |
145 [[snapshotWindow_ contentView] setWantsLayer:YES]; | |
146 [snapshotWindow_ setOpaque:NO]; | |
147 [snapshotWindow_ setBackgroundColor:[NSColor clearColor]]; | |
148 [snapshotWindow_ setAnimationBehavior:NSWindowAnimationBehaviorNone]; | |
149 | |
150 [snapshotWindow_ orderFront:nil]; | |
151 [[[snapshotWindow_ contentView] layer] addSublayer:snapshotLayer_]; | |
152 | |
153 // Compute the frame of the snapshot layer such that the snapshot is | |
154 // positioned exactly on top of the original position of |primaryWindow_|. | |
155 NSRect snapshotLayerFrame = | |
156 [snapshotWindow_ convertRectFromScreen:[primaryWindow_ frame]]; | |
157 [snapshotLayer_ setFrame:snapshotLayerFrame]; | |
158 } | |
159 | |
160 - (void)preparePrimaryWindowForAnimation { | |
161 // Save initial state of |primaryWindow_|. | |
162 primaryWindowInitialFrame_ = [primaryWindow_ frame]; | |
163 primaryWindowInitialBackgroundColor_.reset( | |
164 [[primaryWindow_ backgroundColor] copy]); | |
165 primaryWindowInitialOpaque_ = [primaryWindow_ isOpaque]; | |
166 | |
167 primaryWindowFinalFrame_ = [[primaryWindow_ screen] frame]; | |
168 | |
169 // Make |primaryWindow_| invisible. This must happen before the window is | |
170 // resized, since resizing the window will call drawRect: and cause content | |
171 // to flash over the entire screen. | |
172 [primaryWindow_ setOpaque:NO]; | |
173 [primaryWindow_ setBackgroundColor:[NSColor clearColor]]; | |
174 CALayer* rootLayer = [self rootLayerOfWindow:primaryWindow_]; | |
175 rootLayer.opacity = 0; | |
176 | |
177 // As soon as the style mask includes the flag NSFullScreenWindowMask, the | |
178 // window is expected to receive fullscreen layout. This must be set before | |
179 // the window is resized, as that causes a relayout. | |
180 [primaryWindow_ | |
181 setStyleMask:[primaryWindow_ styleMask] | NSFullScreenWindowMask]; | |
182 | |
183 // Resize |primaryWindow_|. | |
184 changingPrimaryWindowSize_ = YES; | |
185 [primaryWindow_ setFrame:primaryWindowFinalFrame_ display:YES]; | |
186 changingPrimaryWindowSize_ = NO; | |
187 } | |
188 | |
189 - (void)animateSnapshotWindowWithDuration:(CGFloat)duration { | |
190 // Move the snapshot layer until it's bottom-left corner is at the | |
191 // bottom-left corner of the screen. | |
192 CABasicAnimation* positionAnimation = | |
193 [CABasicAnimation animationWithKeyPath:@"position"]; | |
194 positionAnimation.toValue = [NSValue valueWithPoint:NSZeroPoint]; | |
195 positionAnimation.timingFunction = [CAMediaTimingFunction | |
196 functionWithName:kTransformAnimationTimingFunction]; | |
197 | |
198 // Expand the bounds until it covers the screen. | |
199 NSRect finalBounds = NSMakeRect(0, 0, NSWidth(primaryWindowFinalFrame_), | |
200 NSHeight(primaryWindowFinalFrame_)); | |
201 CABasicAnimation* boundsAnimation = | |
202 [CABasicAnimation animationWithKeyPath:@"bounds"]; | |
203 boundsAnimation.toValue = [NSValue valueWithRect:finalBounds]; | |
204 boundsAnimation.timingFunction = [CAMediaTimingFunction | |
205 functionWithName:kTransformAnimationTimingFunction]; | |
206 | |
207 // Fade out the snapshot layer. | |
208 CABasicAnimation* opacityAnimation = | |
209 [CABasicAnimation animationWithKeyPath:@"opacity"]; | |
210 opacityAnimation.toValue = @(0.0); | |
211 opacityAnimation.timingFunction = | |
212 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; | |
213 | |
214 // Fill forwards, and don't remove the animation. When the animation | |
215 // completes, the entire window will be removed. | |
216 CAAnimationGroup* group = [CAAnimationGroup animation]; | |
217 group.removedOnCompletion = NO; | |
218 group.fillMode = kCAFillModeForwards; | |
219 group.animations = @[ positionAnimation, boundsAnimation, opacityAnimation ]; | |
220 group.duration = duration; | |
221 [group setValue:kSnapshotWindowAnimationID forKey:kAnimationIDKey]; | |
222 group.delegate = self; | |
223 | |
224 snapshotAnimation_.reset([group retain]); | |
225 [snapshotLayer_ addAnimation:group forKey:nil]; | |
226 } | |
227 | |
228 - (void)animatePrimaryWindowWithDuration:(CGFloat)duration { | |
229 // As soon as the window's root layer is scaled down, the opacity should be | |
230 // set back to 1. There are a couple of ways to do this. The easiest is to | |
231 // just have a dummy animation as part of the same animation group. | |
232 CABasicAnimation* opacityAnimation = | |
233 [CABasicAnimation animationWithKeyPath:@"opacity"]; | |
234 opacityAnimation.fromValue = @(1.0); | |
235 opacityAnimation.toValue = @(1.0); | |
236 | |
237 // The root layer's size should start scaled down to the initial size of | |
238 // |primaryWindow_|. The animation increases the size until the root layer | |
239 // fills the screen. | |
240 NSRect initialFrame = primaryWindowInitialFrame_; | |
241 NSRect endFrame = primaryWindowFinalFrame_; | |
242 CGFloat xScale = NSWidth(initialFrame) / NSWidth(endFrame); | |
243 CGFloat yScale = NSHeight(initialFrame) / NSHeight(endFrame); | |
244 CATransform3D initial = CATransform3DMakeScale(xScale, yScale, 1); | |
245 CABasicAnimation* transformAnimation = | |
246 [CABasicAnimation animationWithKeyPath:@"transform"]; | |
247 transformAnimation.fromValue = [NSValue valueWithCATransform3D:initial]; | |
248 | |
249 CALayer* root = [self rootLayerOfWindow:primaryWindow_]; | |
250 | |
251 // Calculate the initial position of the root layer. This calculation is | |
252 // agnostic of the anchorPoint. | |
253 CGFloat layerStartPositionDeltaX = NSMidX(initialFrame) - NSMidX(endFrame); | |
254 CGFloat layerStartPositionDeltaY = NSMidY(initialFrame) - NSMidY(endFrame); | |
255 NSPoint layerStartPosition = | |
256 NSMakePoint(root.position.x + layerStartPositionDeltaX, | |
257 root.position.y + layerStartPositionDeltaY); | |
258 | |
259 // Animate the primary window from its initial position. | |
260 CABasicAnimation* positionAnimation = | |
261 [CABasicAnimation animationWithKeyPath:@"position"]; | |
262 positionAnimation.fromValue = [NSValue valueWithPoint:layerStartPosition]; | |
263 | |
264 CAAnimationGroup* group = [CAAnimationGroup animation]; | |
265 group.removedOnCompletion = NO; | |
266 group.fillMode = kCAFillModeForwards; | |
267 group.animations = | |
268 @[ transformAnimation, opacityAnimation, positionAnimation ]; | |
269 group.timingFunction = [CAMediaTimingFunction | |
270 functionWithName:kTransformAnimationTimingFunction]; | |
271 group.duration = duration; | |
272 [group setValue:kPrimaryWindowAnimationID forKey:kAnimationIDKey]; | |
273 group.delegate = self; | |
274 | |
275 primaryWindowAnimation_.reset([group retain]); | |
276 | |
277 [root addAnimation:group forKey:kPrimaryWindowAnimationID]; | |
278 } | |
279 | |
280 - (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag { | |
281 NSString* animationID = [theAnimation valueForKey:kAnimationIDKey]; | |
282 if ([animationID isEqual:kSnapshotWindowAnimationID]) { | |
283 [snapshotWindow_ orderOut:nil]; | |
284 snapshotWindow_.reset(); | |
285 snapshotLayer_.reset(); | |
286 return; | |
287 } | |
288 | |
289 if ([animationID isEqual:kPrimaryWindowAnimationID]) { | |
290 [primaryWindow_ setOpaque:YES]; | |
291 [primaryWindow_ setBackgroundColor:primaryWindowInitialBackgroundColor_]; | |
292 CALayer* root = [self rootLayerOfWindow:primaryWindow_]; | |
293 root.opacity = 1; | |
294 [root removeAnimationForKey:kPrimaryWindowAnimationID]; | |
295 } | |
296 } | |
297 | |
298 - (CALayer*)rootLayerOfWindow:(NSWindow*)window { | |
299 return [[[window contentView] superview] layer]; | |
300 } | |
301 | |
302 @end | |
OLD | NEW |