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 #include "base/mac/scoped_cftyperef.h"
9 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
10 #include "grit/theme_resources.h"
11 #include "ui/base/resource/resource_bundle.h"
12 #include "ui/gfx/image/image.h"
14 #import <QuartzCore/QuartzCore.h>
18 // Constants ///////////////////////////////////////////////////////////////////
20 // The radius of the circle drawn in the shield.
21 const CGFloat kShieldRadius = 70;
23 // The diameter of the circle and the width of its bounding box.
24 const CGFloat kShieldWidth = kShieldRadius * 2;
26 // The height of the shield.
27 const CGFloat kShieldHeight = 140;
29 // Additional height that is added to kShieldHeight when the gesture is
30 // considered complete.
31 const CGFloat kShieldHeightCompletionAdjust = 10;
33 // HistoryOverlayView //////////////////////////////////////////////////////////
35 // The content view that draws the semicircle and the arrow.
36 @interface HistoryOverlayView : NSView {
38 HistoryOverlayMode mode_;
40 base::scoped_nsobject<CAShapeLayer> shapeLayer_;
42 @property(nonatomic) CGFloat shieldAlpha;
43 - (id)initWithMode:(HistoryOverlayMode)mode
44 image:(NSImage*)image;
47 @implementation HistoryOverlayView
49 @synthesize shieldAlpha = shieldAlpha_;
51 - (id)initWithMode:(HistoryOverlayMode)mode
52 image:(NSImage*)image {
53 NSRect frame = NSMakeRect(0, 0, kShieldWidth, kShieldHeight);
54 if ((self = [super initWithFrame:frame])) {
56 shieldAlpha_ = 1.0; // CAShapeLayer's fillColor defaults to opaque black.
58 // A layer-hosting view.
59 shapeLayer_.reset([[CAShapeLayer alloc] init]);
60 [self setLayer:shapeLayer_];
61 [self setWantsLayer:YES];
63 // If going backward, the arrow needs to be in the right half of the circle,
64 // so offset the X position.
65 CGFloat offset = mode_ == kHistoryOverlayModeBack ? kShieldRadius : 0;
66 NSRect arrowRect = NSMakeRect(offset, 0, kShieldRadius, kShieldHeight);
67 arrowRect = NSInsetRect(arrowRect, 10, 0); // Give a little padding.
69 base::scoped_nsobject<NSImageView> imageView(
70 [[NSImageView alloc] initWithFrame:arrowRect]);
71 [imageView setImage:image];
72 [imageView setAutoresizingMask:NSViewMinYMargin | NSViewMaxYMargin];
73 [self addSubview:imageView];
78 - (void)setFrameSize:(CGSize)newSize {
79 NSSize oldSize = [self frame].size;
80 [super setFrameSize:newSize];
82 if (![shapeLayer_ path] || !NSEqualSizes(oldSize, newSize)) {
83 base::ScopedCFTypeRef<CGMutablePathRef> oval(CGPathCreateMutable());
84 CGRect ovalRect = CGRectMake(0, 0, newSize.width, newSize.height);
85 CGPathAddEllipseInRect(oval, nullptr, ovalRect);
86 [shapeLayer_ setPath:oval];
90 - (void)setShieldAlpha:(CGFloat)shieldAlpha {
91 if (shieldAlpha != shieldAlpha_) {
92 shieldAlpha_ = shieldAlpha;
93 base::ScopedCFTypeRef<CGColorRef> fillColor(
94 CGColorCreateGenericGray(0, shieldAlpha));
95 [shapeLayer_ setFillColor:fillColor];
101 // HistoryOverlayController ////////////////////////////////////////////////////
103 @implementation HistoryOverlayController
105 - (id)initForMode:(HistoryOverlayMode)mode {
106 if ((self = [super init])) {
108 DCHECK(mode == kHistoryOverlayModeBack ||
109 mode == kHistoryOverlayModeForward);
115 const gfx::Image& image =
116 ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
117 mode_ == kHistoryOverlayModeBack ? IDR_SWIPE_BACK
118 : IDR_SWIPE_FORWARD);
120 [[HistoryOverlayView alloc] initWithMode:mode_
121 image:image.ToNSImage()]);
122 self.view = contentView_;
125 - (void)setProgress:(CGFloat)gestureAmount finished:(BOOL)finished {
126 NSRect parentFrame = [parent_ frame];
127 // When tracking the gesture, the height is constant and the alpha value
128 // changes from [0.25, 0.65].
129 CGFloat height = kShieldHeight;
130 CGFloat shieldAlpha = std::min(static_cast<CGFloat>(0.65),
131 std::max(gestureAmount,
132 static_cast<CGFloat>(0.25)));
134 // When the gesture is very likely to be completed (90% in this case), grow
135 // the semicircle's height and lock the alpha to 0.75.
137 height += kShieldHeightCompletionAdjust;
141 // Compute the new position based on the progress.
142 NSRect frame = self.view.frame;
143 frame.size.height = height;
144 frame.origin.y = (NSHeight(parentFrame) / 2) - (height / 2);
146 CGFloat width = std::min(kShieldRadius * gestureAmount, kShieldRadius);
147 if (mode_ == kHistoryOverlayModeForward)
148 frame.origin.x = NSMaxX(parentFrame) - width;
149 else if (mode_ == kHistoryOverlayModeBack)
150 frame.origin.x = NSMinX(parentFrame) - kShieldWidth + width;
152 self.view.frame = frame;
153 [contentView_ setShieldAlpha:shieldAlpha];
156 - (void)showPanelForView:(NSView*)view {
157 parent_.reset([view retain]);
158 [self setProgress:0 finished:NO]; // Set initial view position.
159 [parent_ addSubview:self.view];
163 const CGFloat kFadeOutDurationSeconds = 0.4;
165 [NSAnimationContext beginGrouping];
166 [NSAnimationContext currentContext].duration = kFadeOutDurationSeconds;
167 [[self.view animator] removeFromSuperview];
168 [NSAnimationContext endGrouping];