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