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/check.h"
8#include "base/mac/scoped_cftyperef.h"
9#include "chrome/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  base::scoped_nsobject<CAShapeLayer> _shapeLayer;
40}
41@property(nonatomic) CGFloat shieldAlpha;
42- (id)initWithMode:(HistoryOverlayMode)mode
43             image:(NSImage*)image;
44@end
45
46@implementation HistoryOverlayView
47
48@synthesize shieldAlpha = _shieldAlpha;
49
50- (id)initWithMode:(HistoryOverlayMode)mode
51             image:(NSImage*)image {
52  NSRect frame = NSMakeRect(0, 0, kShieldWidth, kShieldHeight);
53  if ((self = [super initWithFrame:frame])) {
54    _mode = mode;
55    _shieldAlpha = 1.0;  // CAShapeLayer's fillColor defaults to opaque black.
56
57    // A layer-hosting view.
58    _shapeLayer.reset([[CAShapeLayer alloc] init]);
59    [self setLayer:_shapeLayer];
60    [self setWantsLayer:YES];
61
62    // If going backward, the arrow needs to be in the right half of the circle,
63    // so offset the X position.
64    CGFloat offset = _mode == kHistoryOverlayModeBack ? kShieldRadius : 0;
65    NSRect arrowRect = NSMakeRect(offset, 0, kShieldRadius, kShieldHeight);
66    arrowRect = NSInsetRect(arrowRect, 10, 0);  // Give a little padding.
67
68    base::scoped_nsobject<NSImageView> imageView(
69        [[NSImageView alloc] initWithFrame:arrowRect]);
70    [imageView setImage:image];
71    [imageView setAutoresizingMask:NSViewMinYMargin | NSViewMaxYMargin];
72    [self addSubview:imageView];
73  }
74  return self;
75}
76
77- (void)setFrameSize:(CGSize)newSize {
78  NSSize oldSize = [self frame].size;
79  [super setFrameSize:newSize];
80
81  if (![_shapeLayer path] || !NSEqualSizes(oldSize, newSize))  {
82    base::ScopedCFTypeRef<CGMutablePathRef> oval(CGPathCreateMutable());
83    CGRect ovalRect = CGRectMake(0, 0, newSize.width, newSize.height);
84    CGPathAddEllipseInRect(oval, nullptr, ovalRect);
85    [_shapeLayer setPath:oval];
86  }
87}
88
89- (void)setShieldAlpha:(CGFloat)shieldAlpha {
90  if (shieldAlpha != _shieldAlpha) {
91    _shieldAlpha = shieldAlpha;
92    base::ScopedCFTypeRef<CGColorRef> fillColor(
93        CGColorCreateGenericGray(0, shieldAlpha));
94    [_shapeLayer setFillColor:fillColor];
95  }
96}
97
98@end
99
100// HistoryOverlayController ////////////////////////////////////////////////////
101
102@implementation HistoryOverlayController
103
104- (id)initForMode:(HistoryOverlayMode)mode {
105  if ((self = [super init])) {
106    _mode = mode;
107    DCHECK(mode == kHistoryOverlayModeBack ||
108           mode == kHistoryOverlayModeForward);
109  }
110  return self;
111}
112
113- (void)loadView {
114  const gfx::Image& image =
115      ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
116          _mode == kHistoryOverlayModeBack ? IDR_SWIPE_BACK
117                                           : IDR_SWIPE_FORWARD);
118  _contentView.reset(
119      [[HistoryOverlayView alloc] initWithMode:_mode
120                                         image:image.ToNSImage()]);
121  self.view = _contentView;
122}
123
124- (void)setProgress:(CGFloat)gestureAmount finished:(BOOL)finished {
125  NSRect parentFrame = [_parent frame];
126  // When tracking the gesture, the height is constant and the alpha value
127  // changes from [0.25, 0.65].
128  CGFloat height = kShieldHeight;
129  CGFloat shieldAlpha = std::min(static_cast<CGFloat>(0.65),
130                                 std::max(gestureAmount,
131                                          static_cast<CGFloat>(0.25)));
132
133  // When the gesture is very likely to be completed (90% in this case), grow
134  // the semicircle's height and lock the alpha to 0.75.
135  if (finished) {
136    height += kShieldHeightCompletionAdjust;
137    shieldAlpha = 0.75;
138  }
139
140  // Compute the new position based on the progress.
141  NSRect frame = self.view.frame;
142  frame.size.height = height;
143  frame.origin.y = (NSHeight(parentFrame) / 2) - (height / 2);
144
145  CGFloat width = std::min(kShieldRadius * gestureAmount, kShieldRadius);
146  if (_mode == kHistoryOverlayModeForward)
147    frame.origin.x = NSMaxX(parentFrame) - width;
148  else if (_mode == kHistoryOverlayModeBack)
149    frame.origin.x = NSMinX(parentFrame) - kShieldWidth + width;
150
151  self.view.frame = frame;
152  [_contentView setShieldAlpha:shieldAlpha];
153}
154
155- (void)showPanelForView:(NSView*)view {
156  _parent.reset([view retain]);
157  [self setProgress:0 finished:NO];  // Set initial view position.
158  [_parent addSubview:self.view];
159}
160
161- (void)dismiss {
162  const CGFloat kFadeOutDurationSeconds = 0.4;
163
164  [NSAnimationContext beginGrouping];
165  [NSAnimationContext currentContext].duration = kFadeOutDurationSeconds;
166  [[self.view animator] removeFromSuperview];
167  [NSAnimationContext endGrouping];
168}
169
170@end
171