• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
21const NSTimeInterval kAnimationDuration = 0.18;
22
23// The number of pixels above the final destination to animate from.
24const CGFloat kShowHideVerticalOffset = 20;
25
26// Scale the window by this factor when animating.
27const CGFloat kShowHideScaleFactor = 0.99;
28
29// Size of the perspective effect as a factor of the window width.
30const CGFloat kShowHidePerspectiveFactor = 0.04;
31
32// Forward declare private CoreGraphics APIs used to transform windows.
33extern "C" {
34
35typedef float float32;
36
37typedef int32 CGSWindow;
38typedef int32 CGSConnection;
39
40typedef struct {
41  float32 x;
42  float32 y;
43} MeshPoint;
44
45typedef struct {
46  MeshPoint local;
47  MeshPoint global;
48} CGPointWarp;
49
50CGSConnection _CGSDefaultConnection();
51CGError CGSSetWindowTransform(const CGSConnection cid,
52                              const CGSWindow wid,
53                              CGAffineTransform transform);
54CGError CGSSetWindowWarp(const CGSConnection cid,
55                         const CGSWindow wid,
56                         int32 w,
57                         int32 h,
58                         CGPointWarp* mesh);
59CGError CGSSetWindowAlpha(const CGSConnection cid,
60                          const CGSWindow wid,
61                          float32 alpha);
62
63}  // extern "C"
64
65namespace {
66
67struct 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.
76NSPoint 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.
92void 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.
99void 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.
122void 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.
131void 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.
179void 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