Index: ios/chrome/browser/ui/popup_menu/popup_menu_controller.mm |
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_controller.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..67844336d01c4c046bc9a25e4611011b1cfdf012 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_controller.mm |
@@ -0,0 +1,287 @@ |
+// Copyright 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 "ios/chrome/browser/ui/popup_menu/popup_menu_controller.h" |
+ |
+#import "base/ios/weak_nsobject.h" |
+#include "base/logging.h" |
+#include "base/mac/bundle_locations.h" |
+#include "base/mac/objc_property_releaser.h" |
+#import "ios/chrome/browser/ui/animation_util.h" |
+#import "ios/chrome/browser/ui/popup_menu/popup_menu_view.h" |
+#include "ios/chrome/browser/ui/rtl_geometry.h" |
+#import "ios/chrome/browser/ui/uikit_ui_util.h" |
+#import "ios/chrome/common/material_timing.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#include "ui/base/l10n/l10n_util.h" |
+ |
+using ios::material::TimingFunction; |
+ |
+namespace { |
+// Inset for the shadows of the contained views. |
+static const CGFloat kPopupMenuVerticalInset = 11.0; |
+// Duration for the Popup Menu Fade In animation |
+static const CGFloat kFadeInAnimationDuration = 0.2; |
+// Value to pass in for backgroundButtonTag to not set a tag value for |
+// |backgroundButton_|. |
+static const NSInteger kBackgroundButtonNoTag = -1; |
+// Default width of the popup container. |
+static const CGFloat kPopupContainerWidth = 236.0; |
+// Default height of the popup container. |
+static const CGFloat kPopupContainerHeight = 280.0; |
+static const CGFloat kPopoverScaleFactor = 0.03; |
+ |
+static void SetAnchorPoint(CGPoint anchorPoint, UIView* view) { |
+ CGPoint oldOrigin = view.frame.origin; |
+ view.layer.anchorPoint = anchorPoint; |
+ CGPoint newOrigin = view.frame.origin; |
+ |
+ CGPoint transition; |
+ transition.x = newOrigin.x - oldOrigin.x; |
+ transition.y = newOrigin.y - oldOrigin.y; |
+ |
+ view.center = |
+ CGPointMake(view.center.x - transition.x, view.center.y - transition.y); |
+} |
+ |
+static CGPoint AnimateInIntermediaryPoint(CGPoint source, CGPoint destination) { |
+ CGPoint midPoint = CGPointZero; |
+ midPoint.x = destination.x; |
+ midPoint.y = source.y - 0.8 * fabs(destination.y - source.y); |
+ return midPoint; |
+} |
+ |
+} // anonymous namespace |
+ |
+@interface PopupMenuController ()<PopupMenuViewDelegate> { |
+ base::mac::ObjCPropertyReleaser propertyReleaser_PopupMenuController_; |
+ CGPoint sourceAnimationPoint_; |
+} |
+@end |
+ |
+@implementation PopupMenuController |
+ |
+@synthesize containerView = containerView_; |
+@synthesize backgroundButton = backgroundButton_; |
+@synthesize popupContainer = popupContainer_; |
+@synthesize delegate = delegate_; |
+ |
+- (id)initWithParentView:(UIView*)parent { |
+ return [self initWithParentView:parent |
+ backgroundButtonParent:nil |
+ backgroundButtonColor:nil |
+ backgroundButtonAlpha:1.0 |
+ backgroundButtonTag:kBackgroundButtonNoTag |
+ backgroundButtonSelector:nil]; |
+} |
+ |
+- (id)initWithParentView:(UIView*)parent |
+ backgroundButtonParent:(UIView*)backgroundButtonParent |
+ backgroundButtonColor:(UIColor*)backgroundButtonColor |
+ backgroundButtonAlpha:(CGFloat)backgroundButtonAlpha |
+ backgroundButtonTag:(NSInteger)backgroundButtonTag |
+ backgroundButtonSelector:(SEL)backgroundButtonSelector { |
+ DCHECK(parent); |
+ self = [super init]; |
+ if (self) { |
+ propertyReleaser_PopupMenuController_.Init(self, |
+ [PopupMenuController class]); |
+ |
+ popupContainer_ = [[PopupMenuView alloc] |
+ initWithFrame:CGRectMake(0, 0, kPopupContainerWidth, |
+ kPopupContainerHeight)]; |
+ |
+ containerView_ = [[UIView alloc] initWithFrame:[parent bounds]]; |
+ containerView_.backgroundColor = [UIColor clearColor]; |
+ [containerView_ setAccessibilityViewIsModal:YES]; |
+ [popupContainer_ setDelegate:self]; |
+ // All views are added to the |containerView_| that in turn is added to the |
+ // parent view. The Container View is needed to have a simple alpha |
+ // transition when the menu is displayed or hidden. |
+ [containerView_ addSubview:popupContainer_]; |
+ [parent addSubview:containerView_]; |
+ |
+ // Initialize backgroundButton_. |
+ UIView* buttonParent = |
+ backgroundButtonParent == nil ? containerView_ : backgroundButtonParent; |
+ backgroundButton_ = [[UIButton alloc] initWithFrame:[buttonParent bounds]]; |
+ [buttonParent addSubview:backgroundButton_]; |
+ if (buttonParent == containerView_) |
+ [buttonParent sendSubviewToBack:backgroundButton_]; |
+ |
+ backgroundButton_.autoresizingMask = |
+ UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth | |
+ UIViewAutoresizingFlexibleBottomMargin | |
+ UIViewAutoresizingFlexibleLeadingMargin(); |
+ backgroundButton_.alpha = backgroundButtonAlpha; |
+ if (backgroundButtonTag != kBackgroundButtonNoTag) |
+ backgroundButton_.tag = backgroundButtonTag; |
+ if (backgroundButtonColor != nil) { |
+ backgroundButton_.backgroundColor = backgroundButtonColor; |
+ } |
+ if (backgroundButtonSelector != nil) { |
+ [backgroundButton_ addTarget:nil |
+ action:backgroundButtonSelector |
+ forControlEvents:UIControlEventTouchDown]; |
+ } else { |
+ [backgroundButton_ addTarget:self |
+ action:@selector(tappedBehindPopup:) |
+ forControlEvents:UIControlEventTouchDown]; |
+ } |
+ [backgroundButton_ setAccessibilityLabel:l10n_util::GetNSString( |
+ IDS_IOS_TOOLBAR_CLOSE_MENU)]; |
+ } |
+ return self; |
+} |
+ |
+- (void)setOptimalSize:(CGSize)optimalSize atOrigin:(CGPoint)origin { |
+ CGRect popupFrame = [popupContainer_ bounds]; |
+ popupFrame.size.width = optimalSize.width; |
+ popupFrame.size.height = 2 * kPopupMenuVerticalInset + optimalSize.height; |
+ |
+ // If the origin is on the right half of the screen, treat origin as the top- |
+ // right coordinate instead of top-left. |
+ CGFloat xOffset = origin.x > [containerView_ bounds].size.width / 2 |
+ ? origin.x - popupFrame.size.width |
+ : origin.x; |
+ [popupContainer_ setFrame:CGRectOffset(popupFrame, xOffset, origin.y)]; |
+} |
+ |
+- (void)fadeInPopupFromSource:(CGPoint)source |
+ toDestination:(CGPoint)destination { |
+ [self animateInFromPoint:source toPoint:destination]; |
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, |
+ containerView_); |
+} |
+ |
+- (void)dismissAnimatedWithCompletion:(void (^)(void))completion { |
+ [self animateOutToPoint:sourceAnimationPoint_ completion:completion]; |
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, |
+ nil); |
+} |
+ |
+// Animate the view on screen (Fade in). |
+- (void)fadeInPopup:(void (^)(BOOL finished))completionBlock { |
+ [UIView animateWithDuration:kFadeInAnimationDuration |
+ delay:0.0 |
+ options:UIViewAnimationOptionAllowUserInteraction |
+ animations:^{ |
+ [containerView_ setAlpha:1.0]; |
+ } |
+ completion:completionBlock]; |
+} |
+ |
+- (void)dealloc { |
+ [popupContainer_ removeFromSuperview]; |
+ [backgroundButton_ removeFromSuperview]; |
+ [containerView_ removeFromSuperview]; |
+ [super dealloc]; |
+} |
+ |
+- (void)tappedBehindPopup:(id)sender { |
+ [self dismissPopupMenu]; |
+} |
+ |
+- (void)animateInFromPoint:(CGPoint)source toPoint:(CGPoint)destination { |
+ sourceAnimationPoint_ = source; |
+ |
+ // Set anchor to top right for top right destinations. |
+ NSUInteger anchorX = |
+ destination.x > [containerView_ bounds].size.width / 2 ? 1 : 0; |
+ SetAnchorPoint(CGPointMake(anchorX, 0), popupContainer_); |
+ |
+ CGPoint midPoint = AnimateInIntermediaryPoint(source, destination); |
+ |
+ CATransform3D scaleTransform = |
+ CATransform3DMakeScale(kPopoverScaleFactor, kPopoverScaleFactor, 1); |
+ |
+ NSValue* destinationScaleValue = |
+ [NSValue valueWithCATransform3D:CATransform3DIdentity]; |
+ |
+ CABasicAnimation* scaleAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"transform"]; |
+ CAMediaTimingFunction* easeOut = TimingFunction(ios::material::CurveEaseOut); |
+ [scaleAnimation setFromValue:[NSValue valueWithCATransform3D:scaleTransform]]; |
+ [scaleAnimation setToValue:destinationScaleValue]; |
+ [scaleAnimation setTimingFunction:easeOut]; |
+ [scaleAnimation setDuration:ios::material::kDuration1]; |
+ |
+ CAKeyframeAnimation* positionAnimation = |
+ [CAKeyframeAnimation animationWithKeyPath:@"position"]; |
+ [positionAnimation setValues:@[ |
+ [NSValue valueWithCGPoint:source], [NSValue valueWithCGPoint:source], |
+ [NSValue valueWithCGPoint:midPoint], [NSValue valueWithCGPoint:destination] |
+ ]]; |
+ [positionAnimation setTimingFunction:easeOut]; |
+ [positionAnimation setDuration:ios::material::kDuration1]; |
+ [positionAnimation setKeyTimes:@[ @0, @0.2, @0.5, @1 ]]; |
+ |
+ CAMediaTimingFunction* linear = TimingFunction(ios::material::CurveLinear); |
+ CAAnimation* fadeAnimation = OpacityAnimationMake(0, 1); |
+ [fadeAnimation setDuration:ios::material::kDuration2]; |
+ [fadeAnimation setTimingFunction:linear]; |
+ [fadeAnimation setBeginTime:ios::material::kDuration2]; |
+ |
+ CALayer* layer = [popupContainer_ layer]; |
+ [layer addAnimation:AnimationGroupMake( |
+ @[ scaleAnimation, positionAnimation, fadeAnimation ]) |
+ forKey:@"popup-in"]; |
+} |
+ |
+- (void)animateOutToPoint:(CGPoint)destination |
+ completion:(void (^)(void))completion { |
+ CGPoint source = [[popupContainer_ layer] position]; |
+ |
+ CGPoint midPoint = CGPointZero; |
+ midPoint.x = destination.x; |
+ midPoint.y = source.y - 0.8 * fabs(destination.x - source.x); |
+ |
+ CATransform3D scaleTransform = |
+ CATransform3DMakeScale(kPopoverScaleFactor, kPopoverScaleFactor, 1); |
+ |
+ CAMediaTimingFunction* easeIn = TimingFunction(ios::material::CurveEaseIn); |
+ [CATransaction begin]; |
+ [CATransaction setAnimationTimingFunction:easeIn]; |
+ [CATransaction setAnimationDuration:ios::material::kDuration2]; |
+ |
+ base::WeakNSObject<PopupMenuController> weakSelf(self); |
+ [CATransaction setCompletionBlock:^{ |
+ if (completion) |
+ completion(); |
+ }]; |
+ |
+ NSValue* sourceScaleValue = |
+ [NSValue valueWithCATransform3D:CATransform3DIdentity]; |
+ |
+ CABasicAnimation* scaleAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"transform"]; |
+ [scaleAnimation setFromValue:sourceScaleValue]; |
+ [scaleAnimation setToValue:[NSValue valueWithCATransform3D:scaleTransform]]; |
+ |
+ CABasicAnimation* positionAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"position"]; |
+ [positionAnimation setFromValue:[NSValue valueWithCGPoint:source]]; |
+ [positionAnimation setToValue:[NSValue valueWithCGPoint:destination]]; |
+ |
+ CABasicAnimation* fadeAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"opacity"]; |
+ [fadeAnimation setFromValue:@1]; |
+ [fadeAnimation setToValue:@0]; |
+ |
+ CALayer* layer = [popupContainer_ layer]; |
+ [layer addAnimation:AnimationGroupMake( |
+ @[ scaleAnimation, positionAnimation, fadeAnimation ]) |
+ forKey:@"out"]; |
+ |
+ [CATransaction commit]; |
+} |
+ |
+#pragma mark - |
+#pragma mark PopupMenuViewDelegate |
+ |
+- (void)dismissPopupMenu { |
+ [delegate_ dismissPopupMenu:self]; |
+} |
+ |
+@end |