// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "chrome/browser/ui/cocoa/history_overlay_controller.h" #include "base/logging.h" #include "base/mac/scoped_cftyperef.h" #import "chrome/browser/ui/cocoa/browser_window_controller.h" #include "grit/theme_resources.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/image/image.h" #import #include // Constants /////////////////////////////////////////////////////////////////// // The radius of the circle drawn in the shield. const CGFloat kShieldRadius = 70; // The diameter of the circle and the width of its bounding box. const CGFloat kShieldWidth = kShieldRadius * 2; // The height of the shield. const CGFloat kShieldHeight = 140; // Additional height that is added to kShieldHeight when the gesture is // considered complete. const CGFloat kShieldHeightCompletionAdjust = 10; // HistoryOverlayView ////////////////////////////////////////////////////////// // The content view that draws the semicircle and the arrow. @interface HistoryOverlayView : NSView { @private HistoryOverlayMode mode_; CGFloat shieldAlpha_; base::scoped_nsobject shapeLayer_; } @property(nonatomic) CGFloat shieldAlpha; - (id)initWithMode:(HistoryOverlayMode)mode image:(NSImage*)image; @end @implementation HistoryOverlayView @synthesize shieldAlpha = shieldAlpha_; - (id)initWithMode:(HistoryOverlayMode)mode image:(NSImage*)image { NSRect frame = NSMakeRect(0, 0, kShieldWidth, kShieldHeight); if ((self = [super initWithFrame:frame])) { mode_ = mode; shieldAlpha_ = 1.0; // CAShapeLayer's fillColor defaults to opaque black. // A layer-hosting view. shapeLayer_.reset([[CAShapeLayer alloc] init]); [self setLayer:shapeLayer_]; [self setWantsLayer:YES]; // If going backward, the arrow needs to be in the right half of the circle, // so offset the X position. CGFloat offset = mode_ == kHistoryOverlayModeBack ? kShieldRadius : 0; NSRect arrowRect = NSMakeRect(offset, 0, kShieldRadius, kShieldHeight); arrowRect = NSInsetRect(arrowRect, 10, 0); // Give a little padding. base::scoped_nsobject imageView( [[NSImageView alloc] initWithFrame:arrowRect]); [imageView setImage:image]; [imageView setAutoresizingMask:NSViewMinYMargin | NSViewMaxYMargin]; [self addSubview:imageView]; } return self; } - (void)setFrameSize:(CGSize)newSize { NSSize oldSize = [self frame].size; [super setFrameSize:newSize]; if (![shapeLayer_ path] || !NSEqualSizes(oldSize, newSize)) { base::ScopedCFTypeRef oval(CGPathCreateMutable()); CGRect ovalRect = CGRectMake(0, 0, newSize.width, newSize.height); CGPathAddEllipseInRect(oval, nullptr, ovalRect); [shapeLayer_ setPath:oval]; } } - (void)setShieldAlpha:(CGFloat)shieldAlpha { if (shieldAlpha != shieldAlpha_) { shieldAlpha_ = shieldAlpha; base::ScopedCFTypeRef fillColor( CGColorCreateGenericGray(0, shieldAlpha)); [shapeLayer_ setFillColor:fillColor]; } } @end // HistoryOverlayController //////////////////////////////////////////////////// @implementation HistoryOverlayController - (id)initForMode:(HistoryOverlayMode)mode { if ((self = [super init])) { mode_ = mode; DCHECK(mode == kHistoryOverlayModeBack || mode == kHistoryOverlayModeForward); } return self; } - (void)loadView { const gfx::Image& image = ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed( mode_ == kHistoryOverlayModeBack ? IDR_SWIPE_BACK : IDR_SWIPE_FORWARD); contentView_.reset( [[HistoryOverlayView alloc] initWithMode:mode_ image:image.ToNSImage()]); self.view = contentView_; } - (void)setProgress:(CGFloat)gestureAmount finished:(BOOL)finished { NSRect parentFrame = [parent_ frame]; // When tracking the gesture, the height is constant and the alpha value // changes from [0.25, 0.65]. CGFloat height = kShieldHeight; CGFloat shieldAlpha = std::min(static_cast(0.65), std::max(gestureAmount, static_cast(0.25))); // When the gesture is very likely to be completed (90% in this case), grow // the semicircle's height and lock the alpha to 0.75. if (finished) { height += kShieldHeightCompletionAdjust; shieldAlpha = 0.75; } // Compute the new position based on the progress. NSRect frame = self.view.frame; frame.size.height = height; frame.origin.y = (NSHeight(parentFrame) / 2) - (height / 2); CGFloat width = std::min(kShieldRadius * gestureAmount, kShieldRadius); if (mode_ == kHistoryOverlayModeForward) frame.origin.x = NSMaxX(parentFrame) - width; else if (mode_ == kHistoryOverlayModeBack) frame.origin.x = NSMinX(parentFrame) - kShieldWidth + width; self.view.frame = frame; [contentView_ setShieldAlpha:shieldAlpha]; } - (void)showPanelForView:(NSView*)view { parent_.reset([view retain]); [self setProgress:0 finished:NO]; // Set initial view position. [parent_ addSubview:self.view]; } - (void)dismiss { const CGFloat kFadeOutDurationSeconds = 0.4; [NSAnimationContext beginGrouping]; [NSAnimationContext currentContext].duration = kFadeOutDurationSeconds; [[self.view animator] removeFromSuperview]; [NSAnimationContext endGrouping]; } @end