| // 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 "ui/base/cocoa/constrained_window/constrained_window_animation.h" |
| |
| #include <stdint.h> |
| #include <stdlib.h> |
| |
| #include "base/files/file_path.h" |
| #include "base/location.h" |
| #import "base/mac/foundation_util.h" |
| #include "base/native_library.h" |
| #include "base/stl_util.h" |
| #include "ui/gfx/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_t CGSWindow; |
| typedef int32_t 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_t w, |
| int32_t 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[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); |
| } |
| |
| bool AreWindowServerEffectsDisabled() { |
| // If the CHROME_HEADLESS env variable is set, this code is running in a |
| // test environment. The custom constrained window animations may be |
| // causing the WindowServer to crash (https://crbug.com/828031), so use the |
| // simple animations. |
| static bool is_headless = getenv("CHROME_HEADLESS") != nullptr; |
| return is_headless; |
| } |
| |
| } // 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 |
| |
| - (instancetype)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]; |
| |
| // Starting in 10.10, the WindowServer forgets to draw the shadow on windows |
| // that animate in this way on retina screens. -[NSWindow invalidateShadow] |
| // doesn't fix it. Neither does toggling -setHasShadow:. But forcing an |
| // update to the window size, and then undoing it, seems to fix the problem. |
| // See http://crbug.com/436884. |
| // TODO(tapted): Find a better fix (this is horrible). |
| NSRect frame = [window_ frame]; |
| [window_ setFrame:NSInsetRect(frame, 1, 1) display:NO animate:NO]; |
| [window_ setFrame:frame display:NO animate:NO]; |
| 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 { |
| if (AreWindowServerEffectsDisabled()) { |
| [window_ setAlphaValue:0.0]; |
| return; |
| } |
| SetWindowAlpha(window_, 0.0); |
| } |
| |
| - (void)setWindowStateForValue:(float)value { |
| if (AreWindowServerEffectsDisabled()) { |
| [window_ setAlphaValue:value]; |
| return; |
| } |
| UpdateWindowShowHideAnimationState(window_, value); |
| } |
| |
| - (void)setWindowStateForEnd { |
| if (AreWindowServerEffectsDisabled()) { |
| [window_ setAlphaValue:1.0]; |
| return; |
| } |
| SetWindowAlpha(window_, 1.0); |
| ClearWindowWarp(window_); |
| } |
| |
| @end |
| |
| @implementation ConstrainedWindowAnimationHide |
| |
| - (void)setWindowStateForValue:(float)value { |
| if (AreWindowServerEffectsDisabled()) { |
| [window_ setAlphaValue:1.0 - value]; |
| return; |
| } |
| UpdateWindowShowHideAnimationState(window_, 1.0 - value); |
| } |
| |
| - (void)setWindowStateForEnd { |
| if (AreWindowServerEffectsDisabled()) { |
| [window_ setAlphaValue:0.0]; |
| return; |
| } |
| SetWindowAlpha(window_, 0.0); |
| ClearWindowWarp(window_); |
| } |
| |
| @end |
| |
| @implementation ConstrainedWindowAnimationPulse |
| |
| // Sets the window scale based on the animation progress. |
| - (void)setWindowStateForValue:(float)value { |
| if (AreWindowServerEffectsDisabled()) |
| return; |
| |
| KeyFrame frames[] = { |
| {0.00, 1.0}, {0.40, 1.02}, {0.60, 1.02}, {1.00, 1.0}, |
| }; |
| |
| CGFloat scale = 1; |
| for (int i = base::size(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 = gfx::Tween::FloatValueBetween(frame_progress, frames[i].scale, |
| frames[i + 1].scale); |
| break; |
| } |
| } |
| |
| SetWindowScale(window_, scale); |
| } |
| |
| - (void)setWindowStateForEnd { |
| if (AreWindowServerEffectsDisabled()) { |
| NSBeep(); |
| return; |
| } |
| |
| SetWindowScale(window_, 1.0); |
| } |
| |
| @end |