1 // Copyright (c) 2012 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/constrained_window/constrained_window_animation.h" 6 7 #include "base/files/file_path.h" 8 #include "base/location.h" 9 #import "base/mac/foundation_util.h" 10 #include "base/native_library.h" 11 #include "ui/gfx/animation/tween.h" 12 13 // The window animations in this file use private APIs as described here: 14 // http://code.google.com/p/undocumented-goodness/source/browse/trunk/CoreGraphics/CGSPrivate.h 15 // There are two important things to keep in mind when modifying this file: 16 // - For most operations the origin of the coordinate system is top left. 17 // - Perspective and shear transformations get clipped if they are bigger 18 // than the window size. This does not seem to apply to scale transformations. 19 20 // Length of the animation in seconds. 21 const NSTimeInterval kAnimationDuration = 0.18; 22 23 // The number of pixels above the final destination to animate from. 24 const CGFloat kShowHideVerticalOffset = 20; 25 26 // Scale the window by this factor when animating. 27 const CGFloat kShowHideScaleFactor = 0.99; 28 29 // Size of the perspective effect as a factor of the window width. 30 const CGFloat kShowHidePerspectiveFactor = 0.04; 31 32 // Forward declare private CoreGraphics APIs used to transform windows. 33 extern "C" { 34 35 typedef float float32; 36 37 typedef int32 CGSWindow; 38 typedef int32 CGSConnection; 39 40 typedef struct { 41 float32 x; 42 float32 y; 43 } MeshPoint; 44 45 typedef struct { 46 MeshPoint local; 47 MeshPoint global; 48 } CGPointWarp; 49 50 CGSConnection _CGSDefaultConnection(); 51 CGError CGSSetWindowTransform(const CGSConnection cid, 52 const CGSWindow wid, 53 CGAffineTransform transform); 54 CGError CGSSetWindowWarp(const CGSConnection cid, 55 const CGSWindow wid, 56 int32 w, 57 int32 h, 58 CGPointWarp* mesh); 59 CGError CGSSetWindowAlpha(const CGSConnection cid, 60 const CGSWindow wid, 61 float32 alpha); 62 63 } // extern "C" 64 65 namespace { 66 67 struct KeyFrame { 68 float value; 69 float scale; 70 }; 71 72 // Get the window location relative to the top left of the main screen. 73 // Most Cocoa APIs use a coordinate system where the screen origin is the 74 // bottom left. The various CGSSetWindow* APIs use a coordinate system where 75 // the screen origin is the top left. 76 NSPoint GetCGSWindowScreenOrigin(NSWindow* window) { 77 NSArray *screens = [NSScreen screens]; 78 if ([screens count] == 0) 79 return NSZeroPoint; 80 // Origin is relative to the screen with the menu bar (the screen at index 0). 81 // Note, this is not the same as mainScreen which is the screen with the key 82 // window. 83 NSScreen* main_screen = [screens objectAtIndex:0]; 84 85 NSRect window_frame = [window frame]; 86 NSRect screen_frame = [main_screen frame]; 87 return NSMakePoint(NSMinX(window_frame), 88 NSHeight(screen_frame) - NSMaxY(window_frame)); 89 } 90 91 // Set the transparency of the window. 92 void SetWindowAlpha(NSWindow* window, float alpha) { 93 CGSConnection cid = _CGSDefaultConnection(); 94 CGSSetWindowAlpha(cid, [window windowNumber], alpha); 95 } 96 97 // Scales the window and translates it so that it stays centered relative 98 // to its original position. 99 void SetWindowScale(NSWindow* window, float scale) { 100 CGFloat scale_delta = 1.0 - scale; 101 CGFloat cur_scale = 1.0 + scale_delta; 102 CGAffineTransform transform = 103 CGAffineTransformMakeScale(cur_scale, cur_scale); 104 105 // Translate the window to keep it centered at the original location. 106 NSSize window_size = [window frame].size; 107 CGFloat scale_offset_x = window_size.width * (1 - cur_scale) / 2.0; 108 CGFloat scale_offset_y = window_size.height * (1 - cur_scale) / 2.0; 109 110 NSPoint origin = GetCGSWindowScreenOrigin(window); 111 CGFloat new_x = -origin.x + scale_offset_x; 112 CGFloat new_y = -origin.y + scale_offset_y; 113 transform = CGAffineTransformTranslate(transform, new_x, new_y); 114 115 CGSConnection cid = _CGSDefaultConnection(); 116 CGSSetWindowTransform(cid, [window windowNumber], transform); 117 } 118 119 // Unsets any window warp that may have been previously applied. 120 // Window warp prevents other effects such as CGSSetWindowTransform from 121 // being applied. 122 void ClearWindowWarp(NSWindow* window) { 123 CGSConnection cid = _CGSDefaultConnection(); 124 CGSSetWindowWarp(cid, [window windowNumber], 0, 0, NULL); 125 } 126 127 // Applies various transformations using a warp effect. The window is 128 // translated vertically by |y_offset|. The window is scaled by |scale| and 129 // translated so that the it remains centered relative to its original position. 130 // Finally, perspective is effect is applied by shrinking the top of the window. 131 void SetWindowWarp(NSWindow* window, 132 float y_offset, 133 float scale, 134 float perspective_offset) { 135 NSRect win_rect = [window frame]; 136 win_rect.origin = NSZeroPoint; 137 NSRect screen_rect = win_rect; 138 screen_rect.origin = GetCGSWindowScreenOrigin(window); 139 140 // Apply a vertical translate. 141 screen_rect.origin.y -= y_offset; 142 143 // Apply a scale and translate to keep the window centered. 144 screen_rect.origin.x += (NSWidth(win_rect) - NSWidth(screen_rect)) / 2.0; 145 screen_rect.origin.y += (NSHeight(win_rect) - NSHeight(screen_rect)) / 2.0; 146 147 // A 2 x 2 mesh that maps each corner of the window to a location in screen 148 // coordinates. Note that the origin of the coordinate system is top, left. 149 CGPointWarp mesh[2][2] = { 150 { 151 { // Top left. 152 {NSMinX(win_rect), NSMinY(win_rect)}, 153 {NSMinX(screen_rect) + perspective_offset, NSMinY(screen_rect)}, 154 }, 155 { // Top right. 156 {NSMaxX(win_rect), NSMinY(win_rect)}, 157 {NSMaxX(screen_rect) - perspective_offset, NSMinY(screen_rect)}, 158 } 159 }, 160 { 161 { // Bottom left. 162 {NSMinX(win_rect), NSMaxY(win_rect)}, 163 {NSMinX(screen_rect), NSMaxY(screen_rect)}, 164 }, 165 { // Bottom right. 166 {NSMaxX(win_rect), NSMaxY(win_rect)}, 167 {NSMaxX(screen_rect), NSMaxY(screen_rect)}, 168 } 169 }, 170 }; 171 172 CGSConnection cid = _CGSDefaultConnection(); 173 CGSSetWindowWarp(cid, [window windowNumber], 2, 2, &(mesh[0][0])); 174 } 175 176 // Sets the various effects that are a part of the Show/Hide animation. 177 // Value is a number between 0 and 1 where 0 means the window is completely 178 // hidden and 1 means the window is fully visible. 179 void UpdateWindowShowHideAnimationState(NSWindow* window, CGFloat value) { 180 CGFloat inverse_value = 1.0 - value; 181 182 SetWindowAlpha(window, value); 183 CGFloat y_offset = kShowHideVerticalOffset * inverse_value; 184 CGFloat scale = 1.0 - (1.0 - kShowHideScaleFactor) * inverse_value; 185 CGFloat perspective_offset = 186 ([window frame].size.width * kShowHidePerspectiveFactor) * inverse_value; 187 188 SetWindowWarp(window, y_offset, scale, perspective_offset); 189 } 190 191 } // namespace 192 193 @interface ConstrainedWindowAnimationBase () 194 // Subclasses should override these to update the window state for the current 195 // animation value. 196 - (void)setWindowStateForStart; 197 - (void)setWindowStateForValue:(float)value; 198 - (void)setWindowStateForEnd; 199 @end 200 201 @implementation ConstrainedWindowAnimationBase 202 203 - (id)initWithWindow:(NSWindow*)window { 204 if ((self = [self initWithDuration:kAnimationDuration 205 animationCurve:NSAnimationEaseInOut])) { 206 window_.reset([window retain]); 207 [self setAnimationBlockingMode:NSAnimationBlocking]; 208 [self setWindowStateForStart]; 209 } 210 return self; 211 } 212 213 - (void)stopAnimation { 214 [super stopAnimation]; 215 [self setWindowStateForEnd]; 216 if ([[self delegate] respondsToSelector:@selector(animationDidEnd:)]) 217 [[self delegate] animationDidEnd:self]; 218 } 219 220 - (void)setCurrentProgress:(NSAnimationProgress)progress { 221 [super setCurrentProgress:progress]; 222 223 if (progress >= 1.0) { 224 [self setWindowStateForEnd]; 225 return; 226 } 227 [self setWindowStateForValue:[self currentValue]]; 228 } 229 230 - (void)setWindowStateForStart { 231 // Subclasses can optionally override this method. 232 } 233 234 - (void)setWindowStateForValue:(float)value { 235 // Subclasses must override this method. 236 NOTREACHED(); 237 } 238 239 - (void)setWindowStateForEnd { 240 // Subclasses can optionally override this method. 241 } 242 243 @end 244 245 @implementation ConstrainedWindowAnimationShow 246 247 - (void)setWindowStateForStart { 248 SetWindowAlpha(window_, 0.0); 249 } 250 251 - (void)setWindowStateForValue:(float)value { 252 UpdateWindowShowHideAnimationState(window_, value); 253 } 254 255 - (void)setWindowStateForEnd { 256 SetWindowAlpha(window_, 1.0); 257 ClearWindowWarp(window_); 258 } 259 260 @end 261 262 @implementation ConstrainedWindowAnimationHide 263 264 - (void)setWindowStateForValue:(float)value { 265 UpdateWindowShowHideAnimationState(window_, 1.0 - value); 266 } 267 268 - (void)setWindowStateForEnd { 269 SetWindowAlpha(window_, 0.0); 270 ClearWindowWarp(window_); 271 } 272 273 @end 274 275 @implementation ConstrainedWindowAnimationPulse 276 277 // Sets the window scale based on the animation progress. 278 - (void)setWindowStateForValue:(float)value { 279 KeyFrame frames[] = { 280 {0.00, 1.0}, 281 {0.40, 1.02}, 282 {0.60, 1.02}, 283 {1.00, 1.0}, 284 }; 285 286 CGFloat scale = 1; 287 for (int i = arraysize(frames) - 1; i >= 0; --i) { 288 if (value >= frames[i].value) { 289 CGFloat delta = frames[i + 1].value - frames[i].value; 290 CGFloat frame_progress = (value - frames[i].value) / delta; 291 scale = gfx::Tween::FloatValueBetween(frame_progress, 292 frames[i].scale, 293 frames[i + 1].scale); 294 break; 295 } 296 } 297 298 SetWindowScale(window_, scale); 299 } 300 301 - (void)setWindowStateForEnd { 302 SetWindowScale(window_, 1.0); 303 } 304 305 @end 306