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 // HistoryOverlayView //////////////////////////////////////////////////////////
34 // The content view that draws the semicircle and the arrow.
35 @interface HistoryOverlayView : NSView {
37 HistoryOverlayMode mode_;
40 @property(nonatomic) CGFloat shieldAlpha;
41 - (id)initWithMode:(HistoryOverlayMode)mode
42 image:(NSImage*)image;
45 @implementation HistoryOverlayView
47 @synthesize shieldAlpha = shieldAlpha_;
49 - (id)initWithMode:(HistoryOverlayMode)mode
50 image:(NSImage*)image {
51 NSRect frame = NSMakeRect(0, 0, kShieldWidth, kShieldHeight);
52 if ((self = [super initWithFrame:frame])) {
55 // If going backward, the arrow needs to be in the right half of the circle,
56 // so offset the X position.
57 CGFloat offset = mode_ == kHistoryOverlayModeBack ? kShieldRadius : 0;
58 NSRect arrowRect = NSMakeRect(offset, 0, kShieldRadius, kShieldHeight);
59 arrowRect = NSInsetRect(arrowRect, 10, 0); // Give a little padding.
61 base::scoped_nsobject<NSImageView> imageView(
62 [[NSImageView alloc] initWithFrame:arrowRect]);
63 [imageView setImage:image];
64 [imageView setAutoresizingMask:NSViewMinYMargin | NSViewMaxYMargin];
65 [self addSubview:imageView];
70 - (void)drawRect:(NSRect)dirtyRect {
71 NSBezierPath* path = [NSBezierPath bezierPathWithOvalInRect:self.bounds];
72 NSColor* fillColor = [NSColor colorWithCalibratedWhite:0 alpha:shieldAlpha_];
79 // HistoryOverlayController ////////////////////////////////////////////////////
81 @implementation HistoryOverlayController
83 - (id)initForMode:(HistoryOverlayMode)mode {
84 if ((self = [super init])) {
86 DCHECK(mode == kHistoryOverlayModeBack ||
87 mode == kHistoryOverlayModeForward);
93 const gfx::Image& image =
94 ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
95 mode_ == kHistoryOverlayModeBack ? IDR_SWIPE_BACK
98 [[HistoryOverlayView alloc] initWithMode:mode_
99 image:image.ToNSImage()]);
100 self.view = contentView_;
103 - (void)setProgress:(CGFloat)gestureAmount finished:(BOOL)finished {
104 NSRect parentFrame = [parent_ frame];
105 // When tracking the gesture, the height is constant and the alpha value
106 // changes from [0.25, 0.65].
107 CGFloat height = kShieldHeight;
108 CGFloat shieldAlpha = std::min(static_cast<CGFloat>(0.65),
109 std::max(gestureAmount,
110 static_cast<CGFloat>(0.25)));
112 // When the gesture is very likely to be completed (90% in this case), grow
113 // the semicircle's height and lock the alpha to 0.75.
115 height += kShieldHeightCompletionAdjust;
119 // Compute the new position based on the progress.
120 NSRect frame = self.view.frame;
121 frame.size.height = height;
122 frame.origin.y = (NSHeight(parentFrame) / 2) - (height / 2);
124 CGFloat width = std::min(kShieldRadius * gestureAmount, kShieldRadius);
125 if (mode_ == kHistoryOverlayModeForward)
126 frame.origin.x = NSMaxX(parentFrame) - width;
127 else if (mode_ == kHistoryOverlayModeBack)
128 frame.origin.x = NSMinX(parentFrame) - kShieldWidth + width;
130 self.view.frame = frame;
131 [contentView_ setShieldAlpha:shieldAlpha];
132 [contentView_ setNeedsDisplay:YES];
135 - (void)showPanelForView:(NSView*)view {
136 parent_.reset([view retain]);
137 [self setProgress:0 finished:NO]; // Set initial view position.
138 [parent_ addSubview:self.view];
139 [[BrowserWindowController
140 browserWindowControllerForView:[self view]] onOverlappedViewShown];
144 const CGFloat kFadeOutDurationSeconds = 0.4;
146 [NSAnimationContext beginGrouping];
147 [NSAnimationContext currentContext].duration = kFadeOutDurationSeconds;
148 NSView* overlay = self.view;
150 base::scoped_nsobject<CAAnimation> animation(
151 [[overlay animationForKey:@"alphaValue"] copy]);
152 [animation setDelegate:self];
153 [animation setDuration:kFadeOutDurationSeconds];
154 NSMutableDictionary* dictionary =
155 [NSMutableDictionary dictionaryWithCapacity:1];
156 [dictionary setObject:animation forKey:@"alphaValue"];
157 [overlay setAnimations:dictionary];
158 [[overlay animator] setAlphaValue:0.0];
159 [NSAnimationContext endGrouping];
162 - (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)finished {
163 [[BrowserWindowController
164 browserWindowControllerForView:[self view]] onOverlappedViewHidden];
165 [self.view removeFromSuperview];
166 // Destroy the CAAnimation and its strong reference to its delegate (this
168 [self.view setAnimations:nil];