// Copyright (c) 2012 The Chromium Authors. All rights reserved. // 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/constrained_window/constrained_window_animation.h" #include "base/files/file_path.h" #include "base/location.h" #import "base/mac/foundation_util.h" #include "base/native_library.h" #include "ui/base/animation/tween.h" // The window animations in this file use private APIs as described here: // http://code.google.com/p/undocumented-goodness/source/browse/trunk/CoreGraphics/CGSPrivate.h // There are two important things to keep in mind when modifying this file: // - For most operations the origin of the coordinate system is top left. // - Perspective and shear transformations get clipped if they are bigger // than the window size. This does not seem to apply to scale transformations. // Length of the animation in seconds. const NSTimeInterval kAnimationDuration = 0.18; // The number of pixels above the final destination to animate from. const CGFloat kShowHideVerticalOffset = 20; // Scale the window by this factor when animating. const CGFloat kShowHideScaleFactor = 0.99; // Size of the perspective effect as a factor of the window width. const CGFloat kShowHidePerspectiveFactor = 0.04; // Forward declare private CoreGraphics APIs used to transform windows. extern "C" { typedef float float32; typedef int32 CGSWindow; typedef int32 CGSConnection; typedef struct { float32 x; float32 y; } MeshPoint; typedef struct { MeshPoint local; MeshPoint global; } CGPointWarp; CGSConnection _CGSDefaultConnection(); CGError CGSSetWindowTransform(const CGSConnection cid, const CGSWindow wid, CGAffineTransform transform); CGError CGSSetWindowWarp(const CGSConnection cid, const CGSWindow wid, int32 w, int32 h, CGPointWarp* mesh); CGError CGSSetWindowAlpha(const CGSConnection cid, const CGSWindow wid, float32 alpha); } // extern "C" namespace { struct KeyFrame { float value; float scale; }; // Get the window location relative to the top left of the main screen. // Most Cocoa APIs use a coordinate system where the screen origin is the // bottom left. The various CGSSetWindow* APIs use a coordinate system where // the screen origin is the top left. NSPoint GetCGSWindowScreenOrigin(NSWindow* window) { NSArray *screens = [NSScreen screens]; if ([screens count] == 0) return NSZeroPoint; // Origin is relative to the screen with the menu bar (the screen at index 0). // Note, this is not the same as mainScreen which is the screen with the key // window. NSScreen* main_screen = [screens objectAtIndex:0]; NSRect window_frame = [window frame]; NSRect screen_frame = [main_screen frame]; return NSMakePoint(NSMinX(window_frame), NSHeight(screen_frame) - NSMaxY(window_frame)); } // Set the transparency of the window. void SetWindowAlpha(NSWindow* window, float alpha) { CGSConnection cid = _CGSDefaultConnection(); CGSSetWindowAlpha(cid, [window windowNumber], alpha); } // Scales the window and translates it so that it stays centered relative // to its original position. void SetWindowScale(NSWindow* window, float scale) { CGFloat scale_delta = 1.0 - scale; CGFloat cur_scale = 1.0 + scale_delta; CGAffineTransform transform = CGAffineTransformMakeScale(cur_scale, cur_scale); // Translate the window to keep it centered at the original location. NSSize window_size = [window frame].size; CGFloat scale_offset_x = window_size.width * (1 - cur_scale) / 2.0; CGFloat scale_offset_y = window_size.height * (1 - cur_scale) / 2.0; NSPoint origin = GetCGSWindowScreenOrigin(window); CGFloat new_x = -origin.x + scale_offset_x; CGFloat new_y = -origin.y + scale_offset_y; transform = CGAffineTransformTranslate(transform, new_x, new_y); CGSConnection cid = _CGSDefaultConnection(); CGSSetWindowTransform(cid, [window windowNumber], transform); } // Unsets any window warp that may have been previously applied. // Window warp prevents other effects such as CGSSetWindowTransform from // being applied. void ClearWindowWarp(NSWindow* window) { CGSConnection cid = _CGSDefaultConnection(); CGSSetWindowWarp(cid, [window windowNumber], 0, 0, NULL); } // Applies various transformations using a warp effect. The window is // translated vertically by |y_offset|. The window is scaled by |scale| and // translated so that the it remains centered relative to its original position. // Finally, perspective is effect is applied by shrinking the top of the window. void SetWindowWarp(NSWindow* window, float y_offset, float scale, float perspective_offset) { NSRect win_rect = [window frame]; win_rect.origin = NSZeroPoint; NSRect screen_rect = win_rect; screen_rect.origin = GetCGSWindowScreenOrigin(window); // Apply a vertical translate. screen_rect.origin.y -= y_offset; // Apply a scale and translate to keep the window centered. screen_rect.origin.x += (NSWidth(win_rect) - NSWidth(screen_rect)) / 2.0; screen_rect.origin.y += (NSHeight(win_rect) - NSHeight(screen_rect)) / 2.0; // A 2 x 2 mesh that maps each corner of the window to a location in screen // coordinates. Note that the origin of the coordinate system is top, left. CGPointWarp mesh[2][2] = { { { // Top left. {NSMinX(win_rect), NSMinY(win_rect)}, {NSMinX(screen_rect) + perspective_offset, NSMinY(screen_rect)}, }, { // Top right. {NSMaxX(win_rect), NSMinY(win_rect)}, {NSMaxX(screen_rect) - perspective_offset, NSMinY(screen_rect)}, } }, { { // Bottom left. {NSMinX(win_rect), NSMaxY(win_rect)}, {NSMinX(screen_rect), NSMaxY(screen_rect)}, }, { // Bottom right. {NSMaxX(win_rect), NSMaxY(win_rect)}, {NSMaxX(screen_rect), NSMaxY(screen_rect)}, } }, }; CGSConnection cid = _CGSDefaultConnection(); CGSSetWindowWarp(cid, [window windowNumber], 2, 2, &(mesh[0][0])); } // Sets the various effects that are a part of the Show/Hide animation. // Value is a number between 0 and 1 where 0 means the window is completely // hidden and 1 means the window is fully visible. void UpdateWindowShowHideAnimationState(NSWindow* window, CGFloat value) { CGFloat inverse_value = 1.0 - value; SetWindowAlpha(window, value); CGFloat y_offset = kShowHideVerticalOffset * inverse_value; CGFloat scale = 1.0 - (1.0 - kShowHideScaleFactor) * inverse_value; CGFloat perspective_offset = ([window frame].size.width * kShowHidePerspectiveFactor) * inverse_value; SetWindowWarp(window, y_offset, scale, perspective_offset); } } // namespace @interface ConstrainedWindowAnimationBase () // Subclasses should override these to update the window state for the current // animation value. - (void)setWindowStateForStart; - (void)setWindowStateForValue:(float)value; - (void)setWindowStateForEnd; @end @implementation ConstrainedWindowAnimationBase - (id)initWithWindow:(NSWindow*)window { if ((self = [self initWithDuration:kAnimationDuration animationCurve:NSAnimationEaseInOut])) { window_.reset([window retain]); [self setAnimationBlockingMode:NSAnimationBlocking]; [self setWindowStateForStart]; } return self; } - (void)stopAnimation { [super stopAnimation]; [self setWindowStateForEnd]; if ([[self delegate] respondsToSelector:@selector(animationDidEnd:)]) [[self delegate] animationDidEnd:self]; } - (void)setCurrentProgress:(NSAnimationProgress)progress { [super setCurrentProgress:progress]; if (progress >= 1.0) { [self setWindowStateForEnd]; return; } [self setWindowStateForValue:[self currentValue]]; } - (void)setWindowStateForStart { // Subclasses can optionally override this method. } - (void)setWindowStateForValue:(float)value { // Subclasses must override this method. NOTREACHED(); } - (void)setWindowStateForEnd { // Subclasses can optionally override this method. } @end @implementation ConstrainedWindowAnimationShow - (void)setWindowStateForStart { SetWindowAlpha(window_, 0.0); } - (void)setWindowStateForValue:(float)value { UpdateWindowShowHideAnimationState(window_, value); } - (void)setWindowStateForEnd { SetWindowAlpha(window_, 1.0); ClearWindowWarp(window_); } @end @implementation ConstrainedWindowAnimationHide - (void)setWindowStateForValue:(float)value { UpdateWindowShowHideAnimationState(window_, 1.0 - value); } - (void)setWindowStateForEnd { SetWindowAlpha(window_, 0.0); ClearWindowWarp(window_); } @end @implementation ConstrainedWindowAnimationPulse // Sets the window scale based on the animation progress. - (void)setWindowStateForValue:(float)value { KeyFrame frames[] = { {0.00, 1.0}, {0.40, 1.02}, {0.60, 1.02}, {1.00, 1.0}, }; CGFloat scale = 1; for (int i = arraysize(frames) - 1; i >= 0; --i) { if (value >= frames[i].value) { CGFloat delta = frames[i + 1].value - frames[i].value; CGFloat frame_progress = (value - frames[i].value) / delta; scale = ui::Tween::ValueBetween(frame_progress, frames[i].scale, frames[i + 1].scale); break; } } SetWindowScale(window_, scale); } - (void)setWindowStateForEnd { SetWindowScale(window_, 1.0); } @end