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/history_overlay_controller.h"
6
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"
12
13#import <QuartzCore/QuartzCore.h>
14
15#include <cmath>
16
17// Constants ///////////////////////////////////////////////////////////////////
18
19// The radius of the circle drawn in the shield.
20const CGFloat kShieldRadius = 70;
21
22// The diameter of the circle and the width of its bounding box.
23const CGFloat kShieldWidth = kShieldRadius * 2;
24
25// The height of the shield.
26const CGFloat kShieldHeight = 140;
27
28// Additional height that is added to kShieldHeight when the gesture is
29// considered complete.
30const CGFloat kShieldHeightCompletionAdjust = 10;
31
32// HistoryOverlayView //////////////////////////////////////////////////////////
33
34// The content view that draws the semicircle and the arrow.
35@interface HistoryOverlayView : NSView {
36 @private
37  HistoryOverlayMode mode_;
38  CGFloat shieldAlpha_;
39}
40@property(nonatomic) CGFloat shieldAlpha;
41- (id)initWithMode:(HistoryOverlayMode)mode
42             image:(NSImage*)image;
43@end
44
45@implementation HistoryOverlayView
46
47@synthesize shieldAlpha = shieldAlpha_;
48
49- (id)initWithMode:(HistoryOverlayMode)mode
50             image:(NSImage*)image {
51  NSRect frame = NSMakeRect(0, 0, kShieldWidth, kShieldHeight);
52  if ((self = [super initWithFrame:frame])) {
53    mode_ = mode;
54
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.
60
61    base::scoped_nsobject<NSImageView> imageView(
62        [[NSImageView alloc] initWithFrame:arrowRect]);
63    [imageView setImage:image];
64    [imageView setAutoresizingMask:NSViewMinYMargin | NSViewMaxYMargin];
65    [self addSubview:imageView];
66  }
67  return self;
68}
69
70- (void)drawRect:(NSRect)dirtyRect {
71  NSBezierPath* path = [NSBezierPath bezierPathWithOvalInRect:self.bounds];
72  NSColor* fillColor = [NSColor colorWithCalibratedWhite:0 alpha:shieldAlpha_];
73  [fillColor set];
74  [path fill];
75}
76
77@end
78
79// HistoryOverlayController ////////////////////////////////////////////////////
80
81@implementation HistoryOverlayController
82
83- (id)initForMode:(HistoryOverlayMode)mode {
84  if ((self = [super init])) {
85    mode_ = mode;
86    DCHECK(mode == kHistoryOverlayModeBack ||
87           mode == kHistoryOverlayModeForward);
88  }
89  return self;
90}
91
92- (void)loadView {
93  const gfx::Image& image =
94      ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
95          mode_ == kHistoryOverlayModeBack ? IDR_SWIPE_BACK
96                                           : IDR_SWIPE_FORWARD);
97  contentView_.reset(
98      [[HistoryOverlayView alloc] initWithMode:mode_
99                                         image:image.ToNSImage()]);
100  self.view = contentView_;
101}
102
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)));
111
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.
114  if (finished) {
115    height += kShieldHeightCompletionAdjust;
116    shieldAlpha = 0.75;
117  }
118
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);
123
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;
129
130  self.view.frame = frame;
131  [contentView_ setShieldAlpha:shieldAlpha];
132  [contentView_ setNeedsDisplay:YES];
133}
134
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}
140
141- (void)dismiss {
142  const CGFloat kFadeOutDurationSeconds = 0.4;
143
144  [NSAnimationContext beginGrouping];
145  [NSAnimationContext currentContext].duration = kFadeOutDurationSeconds;
146  NSView* overlay = self.view;
147
148  base::scoped_nsobject<CAAnimation> animation(
149      [[overlay animationForKey:@"alphaValue"] copy]);
150  [animation setDelegate:self];
151  [animation setDuration:kFadeOutDurationSeconds];
152  NSMutableDictionary* dictionary =
153      [NSMutableDictionary dictionaryWithCapacity:1];
154  [dictionary setObject:animation forKey:@"alphaValue"];
155  [overlay setAnimations:dictionary];
156  [[overlay animator] setAlphaValue:0.0];
157  [NSAnimationContext endGrouping];
158}
159
160- (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)finished {
161  [self.view removeFromSuperview];
162  // Destroy the CAAnimation and its strong reference to its delegate (this
163  // class).
164  [self.view setAnimations:nil];
165}
166
167@end
168