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