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.
5 #import "chrome/browser/ui/cocoa/history_overlay_controller.h"
7 #include "base/logging.h"
8 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
9 #include "grit/theme_resources.h"
10 #include "ui/base/resource/resource_bundle.h"
11 #include "ui/gfx/image/image.h"
13 #import <QuartzCore/QuartzCore.h>
17 // Constants ///////////////////////////////////////////////////////////////////
19 // The radius of the circle drawn in the shield.
20 const CGFloat kShieldRadius = 70;
22 // The diameter of the circle and the width of its bounding box.
23 const CGFloat kShieldWidth = kShieldRadius * 2;
25 // The height of the shield.
26 const CGFloat kShieldHeight = 140;
28 // Additional height that is added to kShieldHeight when the gesture is
29 // considered complete.
30 const CGFloat kShieldHeightCompletionAdjust = 10;
32 // The amount of |gestureAmount| at which AppKit considers the gesture
33 // completed. This was derived more via art than science.
34 const CGFloat kGestureCompleteProgress = 0.3;
36 // HistoryOverlayView //////////////////////////////////////////////////////////
38 // The content view that draws the semicircle and the arrow.
39 @interface HistoryOverlayView : NSView {
41 HistoryOverlayMode mode_;
44 @property(nonatomic) CGFloat shieldAlpha;
45 - (id)initWithMode:(HistoryOverlayMode)mode
46 image:(NSImage*)image;
49 @implementation HistoryOverlayView
51 @synthesize shieldAlpha = shieldAlpha_;
53 - (id)initWithMode:(HistoryOverlayMode)mode
54 image:(NSImage*)image {
55 NSRect frame = NSMakeRect(0, 0, kShieldWidth, kShieldHeight);
56 if ((self = [super initWithFrame:frame])) {
59 // If going backward, the arrow needs to be in the right half of the circle,
60 // so offset the X position.
61 CGFloat offset = mode_ == kHistoryOverlayModeBack ? kShieldRadius : 0;
62 NSRect arrowRect = NSMakeRect(offset, 0, kShieldRadius, kShieldHeight);
63 arrowRect = NSInsetRect(arrowRect, 10, 0); // Give a little padding.
65 base::scoped_nsobject<NSImageView> imageView(
66 [[NSImageView alloc] initWithFrame:arrowRect]);
67 [imageView setImage:image];
68 [imageView setAutoresizingMask:NSViewMinYMargin | NSViewMaxYMargin];
69 [self addSubview:imageView];
74 - (void)drawRect:(NSRect)dirtyRect {
75 NSBezierPath* path = [NSBezierPath bezierPathWithOvalInRect:self.bounds];
76 NSColor* fillColor = [NSColor colorWithCalibratedWhite:0 alpha:shieldAlpha_];
83 // HistoryOverlayController ////////////////////////////////////////////////////
85 @implementation HistoryOverlayController
87 - (id)initForMode:(HistoryOverlayMode)mode {
88 if ((self = [super init])) {
90 DCHECK(mode == kHistoryOverlayModeBack ||
91 mode == kHistoryOverlayModeForward);
97 [[BrowserWindowController
98 browserWindowControllerForView:[self view]] onOverlappedViewHidden];
99 [self.view removeFromSuperview];
104 const gfx::Image& image =
105 ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
106 mode_ == kHistoryOverlayModeBack ? IDR_SWIPE_BACK
107 : IDR_SWIPE_FORWARD);
109 [[HistoryOverlayView alloc] initWithMode:mode_
110 image:image.ToNSImage()]);
111 self.view = contentView_;
114 - (void)setProgress:(CGFloat)gestureAmount {
115 NSRect parentFrame = [parent_ frame];
116 // Scale the gesture amount so that the progress is indicative of the gesture
118 gestureAmount = std::abs(gestureAmount) / kGestureCompleteProgress;
120 // When tracking the gesture, the height is constant and the alpha value
121 // changes from [0.25, 0.65].
122 CGFloat height = kShieldHeight;
123 CGFloat shieldAlpha = std::min(static_cast<CGFloat>(0.65),
124 std::max(gestureAmount,
125 static_cast<CGFloat>(0.25)));
127 // When the gesture is very likely to be completed (90% in this case), grow
128 // the semicircle's height and lock the alpha to 0.75.
129 if (gestureAmount > 0.9) {
130 height += kShieldHeightCompletionAdjust;
134 // Compute the new position based on the progress.
135 NSRect frame = self.view.frame;
136 frame.size.height = height;
137 frame.origin.y = (NSHeight(parentFrame) / 2) - (height / 2);
139 CGFloat width = std::min(kShieldRadius * gestureAmount, kShieldRadius);
140 if (mode_ == kHistoryOverlayModeForward)
141 frame.origin.x = NSMaxX(parentFrame) - width;
142 else if (mode_ == kHistoryOverlayModeBack)
143 frame.origin.x = NSMinX(parentFrame) - kShieldWidth + width;
145 self.view.frame = frame;
146 [contentView_ setShieldAlpha:shieldAlpha];
147 [contentView_ setNeedsDisplay:YES];
150 - (void)showPanelForView:(NSView*)view {
151 parent_.reset([view retain]);
152 [self setProgress:0]; // Set initial view position.
153 [[parent_ superview] addSubview:self.view
154 positioned:NSWindowAbove
156 [[BrowserWindowController
157 browserWindowControllerForView:[self view]] onOverlappedViewShown];
161 const CGFloat kFadeOutDurationSeconds = 0.4;
163 NSView* overlay = self.view;
165 base::scoped_nsobject<CAAnimation> animation(
166 [[overlay animationForKey:@"alphaValue"] copy]);
167 [animation setDelegate:self];
168 [animation setDuration:kFadeOutDurationSeconds];
169 NSMutableDictionary* dictionary =
170 [NSMutableDictionary dictionaryWithCapacity:1];
171 [dictionary setObject:animation forKey:@"alphaValue"];
172 [overlay setAnimations:dictionary];
173 [[overlay animator] setAlphaValue:0.0];
176 - (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)finished {
177 // Destroy the CAAnimation and its strong reference to its delegate (this
179 [self.view setAnimations:nil];