Index: chrome/browser/ui/cocoa/constrained_window/constrained_window_animation.mm |
diff --git a/chrome/browser/ui/cocoa/constrained_window/constrained_window_animation.mm b/chrome/browser/ui/cocoa/constrained_window/constrained_window_animation.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..94efee914a04f70cf4085bcd035347411aea074c |
--- /dev/null |
+++ b/chrome/browser/ui/cocoa/constrained_window/constrained_window_animation.mm |
@@ -0,0 +1,339 @@ |
+// 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/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://goo.gl/CK43l |
+// 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 |
+// then the window size. This does not seem to apply to scale transformations. |
+ |
+// Length of the animation in seconds. |
+const NSTimeInterval kSAnimationDuraiton = 0.18; |
+ |
+// The number of pixels above the final destination to animate from. |
+const CGFloat kShowHideVerticalOffset = 20; |
+ |
+// Scale the window by this percent when animating. |
+const CGFloat kShowHideScalePercent = 0.99; |
+ |
+// Size of the perspective effect as a percent of the window width. |
+const CGFloat kShowHidePerspectivePercent = 0.04; |
+ |
+namespace { |
+ |
+typedef int CGSWindow; |
+typedef int CGSConnection; |
+typedef struct { |
+ float x; |
+ float y; |
+} MeshPoint; |
+typedef struct { |
+ MeshPoint local; |
+ MeshPoint global; |
+} CGPointWarp; |
+ |
+// These are private APIs that we look up at runtime. |
+typedef CGSConnection (*CGSDefaultConnectionFunc)(); |
+typedef CGError (*CGSSetWindowTransformFunc)( |
+ const CGSConnection cid, |
+ const CGSWindow wid, |
+ CGAffineTransform transform); |
+typedef CGError (*CGSSetWindowWarpFunc)( |
+ const CGSConnection cid, |
+ const CGSWindow wid, |
+ int w, |
+ int h, |
+ CGPointWarp* mesh); |
+typedef CGError (*CGSSetWindowAlphaFunc)( |
+ const CGSConnection cid, |
+ const CGSWindow wid, |
+ float alpha); |
+ |
+CGSDefaultConnectionFunc g_CGSDefaultConnection; |
+CGSSetWindowTransformFunc g_CGSSetWindowTransform; |
+CGSSetWindowWarpFunc g_CGSSetWindowWarp; |
+CGSSetWindowAlphaFunc g_CGSSetWindowAlpha; |
+ |
+// Look up private Window APIs. Returns true on success. |
+bool InitializeWindowAPIs() { |
+ bool should_initialize = true; |
+ if (should_initialize) { |
+ should_initialize = false; |
+ CFBundleRef bundle = |
+ CFBundleGetBundleWithIdentifier(CFSTR("com.apple.CoreGraphics")); |
+ if (!bundle) |
+ return false; |
+ |
+ NSArray* names = [NSArray arrayWithObjects: |
+ @"_CGSDefaultConnection", |
+ @"CGSSetWindowTransform", |
+ @"CGSSetWindowWarp", |
+ @"CGSSetWindowAlpha", |
+ nil]; |
+ void* functions[[names count]]; |
+ CFBundleGetFunctionPointersForNames( |
+ bundle, base::mac::NSToCFCast(names), functions); |
+ |
+ g_CGSDefaultConnection = |
+ reinterpret_cast<CGSDefaultConnectionFunc>(functions[0]); |
+ g_CGSSetWindowTransform = |
+ reinterpret_cast<CGSSetWindowTransformFunc>(functions[1]); |
+ g_CGSSetWindowWarp = reinterpret_cast<CGSSetWindowWarpFunc>(functions[2]); |
+ g_CGSSetWindowAlpha = reinterpret_cast<CGSSetWindowAlphaFunc>(functions[3]); |
+ } |
+ return g_CGSDefaultConnection && g_CGSSetWindowTransform && |
+ g_CGSSetWindowWarp && g_CGSSetWindowAlpha; |
+} |
+ |
+// 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; |
+ 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 = g_CGSDefaultConnection(); |
+ g_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 = g_CGSDefaultConnection(); |
+ g_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 = g_CGSDefaultConnection(); |
+ g_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 = g_CGSDefaultConnection(); |
+ g_CGSSetWindowWarp(cid, [window windowNumber], 2, 2, &(mesh[0][0])); |
+} |
+ |
+// Sets the varoius 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 - kShowHideScalePercent) * inverse_value; |
+ CGFloat perspective_offset = |
+ ([window frame].size.width * kShowHidePerspectivePercent) * inverse_value; |
+ |
+ SetWindowWarp(window, y_offset, scale, perspective_offset); |
+} |
+ |
+} |
+ |
+@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 (!InitializeWindowAPIs()) { |
+ [self release]; |
+ return nil; |
+ } |
+ |
+ if ((self = [self initWithDuration:kSAnimationDuraiton |
+ animationCurve:NSAnimationEaseInOut])) { |
+ window_.reset([window retain]); |
+ [self setAnimationBlockingMode:NSAnimationNonblocking]; |
+ [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 { |
+ struct KeyFrame { |
+ float value; |
+ float scale; |
+ }; |
+ KeyFrame frames[] = { |
+ {0.00, 1.0}, |
+ {0.40, 1.02}, |
+ {0.60, 1.02}, |
+ {1.00, 1.0}, |
+ }; |
+ |
+ CGFloat scale = 1; |
+ int count = sizeof(frames) / sizeof(*frames); |
+ for (int i = count - 1; i >= 0; --i) { |
+ if (value >= frames[i].value) { |
+ CGFloat delta = frames[i + 1].value - frames[i].value; |
+ CGFloat percent = (value - frames[i].value) / delta; |
+ scale = ui::Tween::ValueBetween(percent, |
+ frames[i].scale, |
+ frames[i + 1].scale); |
+ break; |
+ } |
+ } |
+ |
+ SetWindowScale(window_, scale); |
+} |
+ |
+- (void)setWindowStateForEnd { |
+ SetWindowScale(window_, 1.0); |
+} |
+ |
+@end |