Home | History | Annotate | Download | only in constrained_window
      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