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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.mm
diff --git a/chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.mm b/chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.mm
new file mode 100644
index 0000000000000000000000000000000000000000..f01efc9c929041d17407ffaa7d37878a6f5b6a5c
--- /dev/null
+++ b/chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.mm
@@ -0,0 +1,293 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
Andre 2015/01/12 06:10:03 2015
erikchen 2015/01/12 19:18:58 Done.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.h"
+
+#include <QuartzCore/QuartzCore.h>
+
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/mac/sdk_forward_declarations.h"
+
+namespace {
+
+NSString* const kPrimaryWindowAnimationID = @"PrimaryWindowAnimationID";
+NSString* const kSnapshotWindowAnimationID = @"SnapshotWindowAnimationID";
+NSString* const kAnimationIDKey = @"AnimationIDKey";
+
+// This class has two simultaneous animations to resize and reposition layers.
+// These animations must use the same timing function, otherwise there will be
+// visual discordance.
+NSString* const kTransformAnimationTimingFunction =
+ kCAMediaTimingFunctionEaseInEaseOut;
+
+} // namespace
+
+@interface BrowserWindowEnterFullscreenTransition () {
+ // The window which is undergoing the fullscreen transition.
+ base::scoped_nsobject<NSWindow> _primaryWindow;
+
+ // A layer that holds a snapshot of the original state of |_primaryWindow|.
+ base::scoped_nsobject<CALayer> _snapshotLayer;
+
+ // A temporary window that holds |_snapshotLayer|.
+ base::scoped_nsobject<NSWindow> _snapshotWindow;
+
+ // The animation applied to |_snapshotLayer|.
+ base::scoped_nsobject<CAAnimationGroup> _snapshotAnimation;
+
+ // The animation applied to the root layer of |_primaryWindow|.
+ base::scoped_nsobject<CAAnimationGroup> _primaryWindowAnimation;
+
+ // The frame of the |_primaryWindow| before the transition began.
+ NSRect _primaryWindowInitialFrame;
+
+ // The background color of |_primaryWindow| before the transition began.
+ base::scoped_nsobject<NSColor> _primaryWindowInitialBackgroundColor;
+
+ // Whether |_primaryWindow| was opaque before the transition began.
+ BOOL _primaryWindowInitialOpaque;
+
+ // Whether the instance is in the process of changing the size of
+ // |_primaryWindow|.
+ BOOL _changingPrimaryWindowSize;
+
+ // The frame that |_primaryWindow| is expected to have after the transition
+ // is finished.
+ NSRect _primaryWindowFinalFrame;
+}
+
+// Takes a snapshot of |_primaryWindow| and puts it in |_snapshotLayer|.
+- (void)takeSnapshot;
+
+// Creates |_snapshotWindow| and adds |_snapshotLayer| to it.
+- (void)makeAndPrepareSnapshotWindow;
+
+// This method has several effects on |_primaryWindow|:
+// - Saves current state.
+// - Makes window transparent, with clear background.
+// - Adds NSFullScreenWindowMask style mask.
+// - Sets the size to the screen's size.
+- (void)preparePrimaryWindowForAnimation;
+
+// Applies the fullscreen animation to |_snapshotLayer|.
+- (void)animateSnapshotWindowWithDuration:(CGFloat)duration;
+
+// Applies the fullscreen animation to the root layer of |_primaryWindow|.
+- (void)animatePrimaryWindowWithDuration:(CGFloat)duration;
+
+// Override of CAAnimation delegate method.
+- (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag;
+
+// Returns the layer of the root view of |window|.
+- (CALayer*)rootLayerOfWindow:(NSWindow*)window;
+
+@end
+
+@implementation BrowserWindowEnterFullscreenTransition
+
+// -------------------------Public Methods----------------------------
+
+- (instancetype)initWithWindow:(NSWindow*)window {
+ DCHECK(window);
+ DCHECK([self rootLayerOfWindow:window]);
+ if ((self = [super init])) {
+ _primaryWindow.reset([window retain]);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [_snapshotAnimation setDelegate:nil];
+ [_primaryWindowAnimation setDelegate:nil];
+ [super dealloc];
+}
+
+- (NSArray*)customWindowsToEnterFullScreen {
+ [self takeSnapshot];
+ [self makeAndPrepareSnapshotWindow];
+ [self preparePrimaryWindowForAnimation];
+ return @[ _primaryWindow.get(), _snapshotWindow.get() ];
+}
+
+- (void)startCustomAnimationToEnterFullScreenWithDuration:
+ (NSTimeInterval)duration {
+ [self animateSnapshotWindowWithDuration:duration];
+ [self animatePrimaryWindowWithDuration:duration];
+}
+
+- (BOOL)shouldWindowBeUnconstrained {
+ return _changingPrimaryWindowSize;
+}
+
+// -------------------------Private Methods----------------------------
+
+- (void)takeSnapshot {
+ base::ScopedCFTypeRef<CGImageRef> windowSnapshot(CGWindowListCreateImage(
+ CGRectNull, kCGWindowListOptionIncludingWindow,
+ [_primaryWindow windowNumber], kCGWindowImageBoundsIgnoreFraming));
+ _snapshotLayer.reset([[CALayer alloc] init]);
+ [_snapshotLayer setFrame:NSRectToCGRect([_primaryWindow frame])];
+ [_snapshotLayer setContents:static_cast<id>(windowSnapshot.get())];
+ [_snapshotLayer setAnchorPoint:CGPointMake(0, 0)];
+ [_snapshotLayer setBackgroundColor:CGColorCreateGenericRGB(0, 0, 0, 0)];
+}
+
+- (void)makeAndPrepareSnapshotWindow {
+ _snapshotWindow.reset(
+ [[NSWindow alloc] initWithContentRect:[[_primaryWindow screen] frame]
+ styleMask:0
+ backing:NSBackingStoreBuffered
+ defer:NO]);
+ [[_snapshotWindow contentView] setWantsLayer:YES];
+ [_snapshotWindow setOpaque:NO];
+ [_snapshotWindow setBackgroundColor:[NSColor clearColor]];
+ [_snapshotWindow setAnimationBehavior:NSWindowAnimationBehaviorNone];
+
+ [_snapshotWindow orderFront:nil];
+ [[[_snapshotWindow contentView] layer] addSublayer:_snapshotLayer];
+ [_snapshotLayer setFrame:[_primaryWindow frame]];
+}
+
+- (void)preparePrimaryWindowForAnimation {
+ // Save initial state of |_primaryWindow|.
+ _primaryWindowInitialFrame = [_primaryWindow frame];
+ _primaryWindowInitialBackgroundColor.reset(
+ [[_primaryWindow backgroundColor] retain]);
+ _primaryWindowInitialOpaque = [_primaryWindow isOpaque];
+
+ _primaryWindowFinalFrame = [[_primaryWindow screen] frame];
+
+ // Make |_primaryWindow| invisible. This must happen before the window is
+ // resized.
+ [_primaryWindow setOpaque:NO];
+ [_primaryWindow setBackgroundColor:[NSColor clearColor]];
+ CALayer* rootLayer = [self rootLayerOfWindow:_primaryWindow];
+ rootLayer.opacity = 0;
+
+ // As soon as the style mask includes the flag NSFullScreenWindowMask, the
+ // window is expected to receive fullscreen layout. This must be set before
+ // the window is resized, as that causes a relayout.
+ [_primaryWindow
+ setStyleMask:[_primaryWindow styleMask] | NSFullScreenWindowMask];
+
+ // Resize |_primaryWindow|.
+ _changingPrimaryWindowSize = YES;
+ [_primaryWindow setFrame:_primaryWindowFinalFrame display:YES];
+ _changingPrimaryWindowSize = NO;
+}
+
+- (void)animateSnapshotWindowWithDuration:(CGFloat)duration {
+ // Move the snapshot layer until it's bottom-left corner is at the
+ // bottom-left corner of the screen.
+ CABasicAnimation* positionAnimation =
+ [CABasicAnimation animationWithKeyPath:@"position"];
+ positionAnimation.toValue = [NSValue valueWithPoint:NSMakePoint(0, 0)];
Andre 2015/01/12 06:10:03 NSZeroPoint
erikchen 2015/01/12 19:18:58 Done.
+ positionAnimation.timingFunction = [CAMediaTimingFunction
+ functionWithName:kTransformAnimationTimingFunction];
+
+ // Expand the bounds until it covers the screen.
+ NSRect finalBounds = NSMakeRect(0, 0, NSWidth(_primaryWindowFinalFrame),
+ NSHeight(_primaryWindowFinalFrame));
+ CABasicAnimation* boundsAnimation =
+ [CABasicAnimation animationWithKeyPath:@"bounds"];
+ boundsAnimation.toValue = [NSValue valueWithRect:finalBounds];
+ boundsAnimation.timingFunction = [CAMediaTimingFunction
+ functionWithName:kTransformAnimationTimingFunction];
+
+ // Fade out the snapshot layer.
+ CABasicAnimation* opacityAnimation =
+ [CABasicAnimation animationWithKeyPath:@"opacity"];
+ 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.
+ opacityAnimation.timingFunction =
+ [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
+
+ // Fill forwards, and don't remove the animation. When the animation
+ // completes, the entire window will be removed.
+ CAAnimationGroup* group = [CAAnimationGroup animation];
+ group.removedOnCompletion = NO;
+ group.fillMode = kCAFillModeForwards;
+ group.animations = @[ positionAnimation, boundsAnimation, opacityAnimation ];
+ group.duration = duration;
+ [group setValue:kSnapshotWindowAnimationID forKey:kAnimationIDKey];
+ group.delegate = self;
+
+ _snapshotAnimation.reset([group retain]);
+ [_snapshotLayer addAnimation:group forKey:nil];
+}
+
+- (void)animatePrimaryWindowWithDuration:(CGFloat)duration {
+ // As soon as the window is downsized, the opacity should be set back to 1.
+ // There are a couple of ways to do this. The easiest is to just have a dummy
+ // animation as part of the same animation group.
+ CABasicAnimation* opacityAnimation =
+ [CABasicAnimation animationWithKeyPath:@"opacity"];
+ opacityAnimation.fromValue = @(1);
+ opacityAnimation.toValue = @(1);
+
+ // Downsize the primary window, and the increase the size until it reaches it
+ // expected end size.
+ NSRect initialFrame = _primaryWindowInitialFrame;
+ NSRect endFrame = _primaryWindowFinalFrame;
+ CGFloat xScale = initialFrame.size.width / endFrame.size.width;
+ 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.
+ CATransform3D initial = CATransform3DMakeScale(xScale, yScale, 1);
+ CABasicAnimation* transformAnimation =
+ [CABasicAnimation animationWithKeyPath:@"transform"];
+ transformAnimation.fromValue = [NSValue valueWithCATransform3D:initial];
+ transformAnimation.toValue =
+ [NSValue valueWithCATransform3D:CATransform3DIdentity];
+ transformAnimation.timingFunction = [CAMediaTimingFunction
+ functionWithName:kTransformAnimationTimingFunction];
+
+ // Animate the primary window to its final position.
+ CABasicAnimation* positionAnimation =
+ [CABasicAnimation animationWithKeyPath:@"position"];
+ CGPoint initialCenter =
+ CGPointMake(CGRectGetMidX(initialFrame), CGRectGetMidY(initialFrame));
+ CGPoint finalCenter =
+ 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.
+ positionAnimation.fromValue = [NSValue valueWithPoint:initialCenter];
+ positionAnimation.toValue = [NSValue valueWithPoint:finalCenter];
+ positionAnimation.timingFunction = [CAMediaTimingFunction
+ functionWithName:kTransformAnimationTimingFunction];
+
+ CAAnimationGroup* group = [CAAnimationGroup animation];
+ group.removedOnCompletion = NO;
+ group.fillMode = kCAFillModeForwards;
+ group.animations =
+ @[ transformAnimation, opacityAnimation, positionAnimation ];
+ group.duration = duration;
+ [group setValue:kPrimaryWindowAnimationID forKey:kAnimationIDKey];
+ group.delegate = self;
+
+ _primaryWindowAnimation.reset([group retain]);
+ CALayer* root = [self rootLayerOfWindow:_primaryWindow];
+
+ [root addAnimation:group forKey:kPrimaryWindowAnimationID];
+}
+
+- (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag {
+ NSString* animationID = [theAnimation valueForKey:kAnimationIDKey];
+ if ([animationID isEqual:kSnapshotWindowAnimationID]) {
+ [_snapshotWindow orderOut:nil];
+ _snapshotWindow.reset();
+ _snapshotLayer.reset();
+ return;
+ }
+
+ if ([animationID isEqual:kPrimaryWindowAnimationID]) {
+ [_primaryWindow setOpaque:YES];
+ [_primaryWindow setBackgroundColor:_primaryWindowInitialBackgroundColor];
+ CALayer* root = [self rootLayerOfWindow:_primaryWindow];
+ root.opacity = 1;
+ [root removeAnimationForKey:kPrimaryWindowAnimationID];
+ }
+}
+
+- (CALayer*)rootLayerOfWindow:(NSWindow*)window {
+ return [[[window contentView] superview] layer];
+}
+
+@end

Powered by Google App Engine
This is Rietveld 408576698