Index: ios/chrome/browser/ui/omnibox/page_info_view_controller.mm |
diff --git a/ios/chrome/browser/ui/omnibox/page_info_view_controller.mm b/ios/chrome/browser/ui/omnibox/page_info_view_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..78fb2a5a7b825d9bb004acf85b47c11bf6fdea45 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/omnibox/page_info_view_controller.mm |
@@ -0,0 +1,637 @@ |
+// 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 "ios/chrome/browser/ui/omnibox/page_info_view_controller.h" |
+ |
+#include "base/bind.h" |
+#include "base/location.h" |
+#include "base/mac/bundle_locations.h" |
+#import "base/mac/foundation_util.h" |
+#include "base/mac/objc_property_releaser.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/message_loop/message_loop.h" |
+#include "base/single_thread_task_runner.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/threading/thread_task_runner_handle.h" |
+#include "components/strings/grit/components_strings.h" |
+#import "ios/chrome/browser/ui/animation_util.h" |
+#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
+#include "ios/chrome/browser/ui/commands/ios_command_ids.h" |
+#import "ios/chrome/browser/ui/fancy_ui/bidi_container_view.h" |
+#include "ios/chrome/browser/ui/omnibox/page_info_model.h" |
+#import "ios/chrome/browser/ui/popup_menu/popup_menu_view.h" |
+#include "ios/chrome/browser/ui/rtl_geometry.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#import "ios/chrome/browser/ui/uikit_ui_util.h" |
+#import "ios/chrome/common/material_timing.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h" |
+#import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoFontLoader.h" |
+#include "ui/base/l10n/l10n_util.h" |
+#include "ui/base/l10n/l10n_util_mac.h" |
+#import "ui/gfx/ios/NSString+CrStringDrawing.h" |
+#import "ui/gfx/ios/uikit_util.h" |
+ |
+using ios::material::TimingFunction; |
+ |
+namespace { |
+ |
+// The width of the view. |
+const CGFloat kViewWidthRegular = 600.0; |
+const CGFloat kViewWidthCompact = 288.0; |
+const CGFloat kViewWidthiPhoneLandscape = 400.0; |
+// Spacing in between sections. |
+const CGFloat kVerticalSpacing = 20.0; |
+// Initial position for the left side of the frame. |
+const CGFloat kInitialFramePosition = 8.0; |
+// Alpha for the shield. |
+const CGFloat kShieldAlpha = 0.5; |
+// Scroll View inset. |
+const CGFloat kScrollViewInset = 5.0; |
+// The size of the footer (rounded corner and shadow) for page info view. |
+const CGFloat kPageInfoViewFooterSize = 15.0; |
+// Padding between the window frame and content. |
+const CGFloat kFramePadding = 24; |
+// Padding for the initial line of the view. |
+const CGFloat kInitialLinePadding = 40; |
+// Padding between the bottom of the content and the window frame. |
+const CGFloat kFrameBottomPadding = 16; |
+// Spacing between the optional headline and description text views. |
+const CGFloat kHeadlineSpacing = 16; |
+// Spacing between the image and the text. |
+const CGFloat kImageSpacing = 16; |
+// Square size of the image. |
+const CGFloat kImageSize = 24; |
+// The height of the headline label. |
+const CGFloat kHeadlineHeight = 19; |
+// The hex color for the help button text. |
+const int kPageInfoHelpButtonRGB = 0x3b8cfe; |
+// The grey scale color for the text within the page info alert. |
+const CGFloat kPageInfoTextGreyComponent = 0.2; |
+ |
+inline UIColor* PageInfoTextColor() { |
+ return [UIColor colorWithWhite:kPageInfoTextGreyComponent alpha:1]; |
+} |
+ |
+inline UIColor* PageInfoHelpButtonColor() { |
+ return UIColorFromRGB(kPageInfoHelpButtonRGB); |
+} |
+ |
+inline UIFont* PageInfoHeadlineFont() { |
+ return [[MDFRobotoFontLoader sharedInstance] mediumFontOfSize:16]; |
+} |
+ |
+inline CATransform3D PageInfoAnimationScale() { |
+ return CATransform3DMakeScale(0.03, 0.03, 1); |
+} |
+ |
+// Offset to make sure image aligns with the header line. |
+inline CGFloat PageInfoImageVerticalOffset() { |
+ return ui::AlignValueToUpperPixel((kHeadlineHeight - kImageSize) / 2.0); |
+} |
+ |
+// The X position of the text fields. Variants for with and without an image. |
+const CGFloat kTextXPositionNoImage = kFramePadding; |
+const CGFloat kTextXPosition = |
+ kTextXPositionNoImage + kImageSize + kImageSpacing; |
+ |
+// The X offset for the help button. |
+const CGFloat kButtonXOffset = kTextXPosition; |
+ |
+} // namespace |
+ |
+PageInfoModelBubbleBridge::PageInfoModelBubbleBridge() |
+ : controller_(nil), weak_ptr_factory_(this) {} |
+ |
+PageInfoModelBubbleBridge::~PageInfoModelBubbleBridge() {} |
+ |
+void PageInfoModelBubbleBridge::OnPageInfoModelChanged() { |
+ // Check to see if a layout has already been scheduled. |
+ if (weak_ptr_factory_.HasWeakPtrs()) |
+ return; |
+ |
+ // Delay performing layout by a second so that all the animations from |
+ // InfoBubbleWindow and origin updates from BaseBubbleController finish, so |
+ // that we don't all race trying to change the frame's origin. |
+ // |
+ // Using MessageLoop is superior here to |-performSelector:| because it will |
+ // not retain its target; if the child outlives its parent, zombies get left |
+ // behind (http://crbug.com/59619). This will cancel the scheduled task if |
+ // the controller (and thus this bridge) get destroyed before the message |
+ // can be delivered. |
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
+ FROM_HERE, base::Bind(&PageInfoModelBubbleBridge::PerformLayout, |
+ weak_ptr_factory_.GetWeakPtr()), |
+ base::TimeDelta::FromMilliseconds(1000 /* milliseconds */)); |
+} |
+ |
+@interface PageInfoViewController ()<UIGestureRecognizerDelegate> { |
+ // Scroll View inside the PageInfoView used to display content that exceeds |
+ // the available space. |
+ base::scoped_nsobject<UIScrollView> scrollView_; |
+ // Container View added inside the Scroll View. All content is added to this |
+ // view instead of PopupMenuController.containerView_. |
+ base::scoped_nsobject<BidiContainerView> innerContainerView_; |
+ |
+ // Origin of the arrow at the top of the popup window. |
+ CGPoint origin_; |
+ |
+ // Model for the data to display. |
+ std::unique_ptr<PageInfoModel> model_; |
+ |
+ // Thin bridge that pushes model-changed notifications from C++ to Cocoa. |
+ std::unique_ptr<PageInfoModelObserver> bridge_; |
+ |
+ // Width of the view. Depends on the device (iPad/iPhone). |
+ CGFloat viewWidth_; |
+ |
+ // Width of the text fields. |
+ CGFloat textWidth_; |
+ |
+ // YES when the popup has finished animating in. NO otherwise. |
+ BOOL animateInCompleted_; |
+ |
+ base::mac::ObjCPropertyReleaser propertyReleaser_PageInfoViewController_; |
+} |
+ |
+// Adds the state image at a pre-determined x position and the given y. This |
+// does not affect the next Y position because the image is placed next to |
+// a text field that is larger and accounts for the image's size. |
+- (void)addImageViewForInfo:(const PageInfoModel::SectionInfo&)info |
+ toSubviews:(NSMutableArray*)subviews |
+ atOffset:(CGFloat)offset; |
+ |
+// Adds the title text field at the given x,y position, and returns the y |
+// position for the next element. |
+- (CGFloat)addHeadlineViewForInfo:(const PageInfoModel::SectionInfo&)info |
+ toSubviews:(NSMutableArray*)subviews |
+ atPoint:(CGPoint)point; |
+ |
+// Adds the description text field at the given x,y position, and returns the y |
+// position for the next element. |
+- (CGFloat)addDescriptionViewForInfo:(const PageInfoModel::SectionInfo&)info |
+ toSubviews:(NSMutableArray*)subviews |
+ atPoint:(CGPoint)point; |
+ |
+// Returns a button with title and action configured for |buttonAction|. |
+- (UIButton*)buttonForAction:(PageInfoModel::ButtonAction)buttonAction; |
+ |
+// Adds the the button |buttonAction| that explains the icons. Returns the y |
+// position delta for the next offset. |
+- (CGFloat)addButton:(PageInfoModel::ButtonAction)buttonAction |
+ toSubviews:(NSMutableArray*)subviews |
+ atOffset:(CGFloat)offset; |
+ |
+@property(nonatomic, retain) UIView* containerView; |
+@property(nonatomic, retain) UIView* popupContainer; |
+@end |
+ |
+@implementation PageInfoViewController |
+ |
+@synthesize containerView = containerView_; |
+@synthesize popupContainer = popupContainer_; |
+ |
+- (id)initWithModel:(PageInfoModel*)model |
+ bridge:(PageInfoModelObserver*)bridge |
+ sourceFrame:(CGRect)source |
+ parentView:(UIView*)parent { |
+ DCHECK(parent); |
+ self = [super init]; |
+ if (self) { |
+ propertyReleaser_PageInfoViewController_.Init( |
+ self, [PageInfoViewController class]); |
+ |
+ scrollView_.reset( |
+ [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 240, 128)]); |
+ [scrollView_ setMultipleTouchEnabled:YES]; |
+ [scrollView_ setClipsToBounds:YES]; |
+ [scrollView_ setShowsHorizontalScrollIndicator:NO]; |
+ [scrollView_ setIndicatorStyle:UIScrollViewIndicatorStyleBlack]; |
+ [scrollView_ |
+ setAutoresizingMask:(UIViewAutoresizingFlexibleTrailingMargin() | |
+ UIViewAutoresizingFlexibleTopMargin)]; |
+ |
+ innerContainerView_.reset( |
+ [[BidiContainerView alloc] initWithFrame:CGRectMake(0, 0, 194, 327)]); |
+ [innerContainerView_ setBackgroundColor:[UIColor clearColor]]; |
+ [innerContainerView_ |
+ setAccessibilityLabel:@"Page Security Info Scroll Container"]; |
+ [innerContainerView_ |
+ setAutoresizingMask:(UIViewAutoresizingFlexibleTrailingMargin() | |
+ UIViewAutoresizingFlexibleBottomMargin)]; |
+ |
+ model_.reset(model); |
+ bridge_.reset(bridge); |
+ origin_ = CGPointMake(CGRectGetMidX(source), CGRectGetMaxY(source)); |
+ |
+ UIInterfaceOrientation orientation = |
+ [[UIApplication sharedApplication] statusBarOrientation]; |
+ viewWidth_ = IsCompact() ? kViewWidthCompact : kViewWidthRegular; |
+ // Special case iPhone landscape. |
+ if (!IsIPadIdiom() && UIInterfaceOrientationIsLandscape(orientation)) |
+ viewWidth_ = kViewWidthiPhoneLandscape; |
+ |
+ textWidth_ = viewWidth_ - (kImageSize + kImageSpacing + kFramePadding * 2 + |
+ kScrollViewInset * 2); |
+ |
+ base::scoped_nsobject<UILongPressGestureRecognizer> touchDownRecognizer( |
+ [[UILongPressGestureRecognizer alloc] |
+ initWithTarget:self |
+ action:@selector(rootViewTapped:)]); |
+ // Setting the duration to .001 makes this similar to a control event |
+ // UIControlEventTouchDown. |
+ [touchDownRecognizer setMinimumPressDuration:.001]; |
+ [touchDownRecognizer setDelegate:self]; |
+ |
+ containerView_ = [[UIView alloc] initWithFrame:[parent bounds]]; |
+ [containerView_ addGestureRecognizer:touchDownRecognizer]; |
+ [containerView_ |
+ setBackgroundColor:[UIColor colorWithWhite:0 alpha:kShieldAlpha]]; |
+ [containerView_ setTag:IDC_HIDE_PAGE_INFO]; |
+ [containerView_ setOpaque:NO]; |
+ [containerView_ setAlpha:0]; |
+ [containerView_ setAccessibilityViewIsModal:YES]; |
+ |
+ popupContainer_ = [[UIView alloc] initWithFrame:CGRectZero]; |
+ [popupContainer_ setBackgroundColor:[UIColor whiteColor]]; |
+ [popupContainer_ setClipsToBounds:YES]; |
+ [containerView_ addSubview:popupContainer_]; |
+ |
+ [self.popupContainer addSubview:scrollView_]; |
+ [scrollView_ addSubview:innerContainerView_]; |
+ [scrollView_ setAccessibilityIdentifier:@"Page Security Scroll View"]; |
+ [parent addSubview:self.containerView]; |
+ [self performLayout]; |
+ |
+ [self animatePageInfoViewIn:source]; |
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, |
+ containerView_); |
+ } |
+ |
+ return self; |
+} |
+ |
+- (void)performLayout { |
+ CGFloat offset = kInitialLinePadding; |
+ |
+ // Keep the new subviews in an array that gets replaced at the end. |
+ NSMutableArray* subviews = [NSMutableArray array]; |
+ |
+ int sectionCount = model_->GetSectionCount(); |
+ PageInfoModel::ButtonAction action = PageInfoModel::BUTTON_NONE; |
+ |
+ for (int i = 0; i < sectionCount; i++) { |
+ PageInfoModel::SectionInfo info = model_->GetSectionInfo(i); |
+ |
+ if (action == PageInfoModel::BUTTON_NONE && |
+ info.button != PageInfoModel::BUTTON_NONE) { |
+ // Show the button corresponding to the first section that requires a |
+ // button. |
+ action = info.button; |
+ } |
+ |
+ // Only certain sections have images. This affects the X position. |
+ BOOL hasImage = model_->GetIconImage(info.icon_id) != nil; |
+ CGFloat xPosition = (hasImage ? kTextXPosition : kTextXPositionNoImage); |
+ |
+ // Insert the image subview for sections that are appropriate. |
+ CGFloat imageBaseline = offset + kImageSize; |
+ if (hasImage) { |
+ [self addImageViewForInfo:info |
+ toSubviews:subviews |
+ atOffset:offset + PageInfoImageVerticalOffset()]; |
+ } |
+ |
+ // Add the title. |
+ if (!info.headline.empty()) { |
+ offset += [self addHeadlineViewForInfo:info |
+ toSubviews:subviews |
+ atPoint:CGPointMake(xPosition, offset)]; |
+ offset += kHeadlineSpacing; |
+ } |
+ |
+ // Create the description of the state. |
+ offset += [self addDescriptionViewForInfo:info |
+ toSubviews:subviews |
+ atPoint:CGPointMake(xPosition, offset)]; |
+ |
+ // If at this point the description and optional headline and button are |
+ // not as tall as the image, adjust the offset by the difference. |
+ CGFloat imageBaselineDelta = imageBaseline - offset; |
+ if (imageBaselineDelta > 0) |
+ offset += imageBaselineDelta; |
+ |
+ // Add the separators. |
+ int testSectionCount = sectionCount - 1; |
+ if (i != testSectionCount || |
+ (i == testSectionCount && action != PageInfoModel::BUTTON_NONE)) { |
+ offset += kVerticalSpacing; |
+ } |
+ } |
+ |
+ // The last item at the bottom of the window is the help center link. Do not |
+ // show this for the internal pages, which have one section. |
+ offset += [self addButton:action toSubviews:subviews atOffset:offset]; |
+ |
+ // Add the bottom padding. |
+ offset += kVerticalSpacing; |
+ CGRect frame = |
+ CGRectMake(kInitialFramePosition, origin_.y, viewWidth_, offset); |
+ |
+ // Increase the size of the frame by the amount used for drawing rounded |
+ // corners and shadow. |
+ frame.size.height += kPageInfoViewFooterSize; |
+ |
+ if (CGRectGetMaxY(frame) > |
+ CGRectGetMaxY([[self containerView] superview].bounds) - |
+ kFrameBottomPadding) { |
+ // If the frame is bigger than the parent view than change the frame to |
+ // fit in the superview bounds. |
+ frame.size.height = [[self containerView] superview].bounds.size.height - |
+ kFrameBottomPadding - frame.origin.y; |
+ |
+ [scrollView_ setScrollEnabled:YES]; |
+ [scrollView_ flashScrollIndicators]; |
+ } else { |
+ [scrollView_ setScrollEnabled:NO]; |
+ } |
+ |
+ CGRect containerBounds = [containerView_ bounds]; |
+ CGRect popupFrame = frame; |
+ popupFrame.origin.x = |
+ CGRectGetMidX(containerBounds) - CGRectGetWidth(popupFrame) / 2.0; |
+ popupFrame.origin.y = |
+ CGRectGetMidY(containerBounds) - CGRectGetHeight(popupFrame) / 2.0; |
+ |
+ popupFrame.origin = AlignPointToPixel(popupFrame.origin); |
+ CGRect innerFrame = CGRectMake(0, 0, popupFrame.size.width, offset); |
+ |
+ // If the initial animation has completed, animate the new frames. |
+ if (animateInCompleted_) { |
+ [UIView cr_animateWithDuration:ios::material::kDuration3 |
+ delay:0 |
+ curve:ios::material::CurveEaseInOut |
+ options:0 |
+ animations:^{ |
+ [popupContainer_ setFrame:popupFrame]; |
+ [scrollView_ setFrame:[popupContainer_ bounds]]; |
+ [innerContainerView_ setFrame:innerFrame]; |
+ } |
+ completion:nil]; |
+ } else { |
+ // Popup hasn't finished animating in yet. Set frames immediately. |
+ [popupContainer_ setFrame:popupFrame]; |
+ [scrollView_ setFrame:[popupContainer_ bounds]]; |
+ [innerContainerView_ setFrame:innerFrame]; |
+ } |
+ |
+ for (UIView* view in [innerContainerView_ subviews]) { |
+ [view removeFromSuperview]; |
+ } |
+ |
+ for (UIView* view in subviews) { |
+ [innerContainerView_ addSubview:view]; |
+ [innerContainerView_ setSubviewNeedsAdjustmentForRTL:view]; |
+ } |
+ |
+ [scrollView_ setContentSize:innerContainerView_.get().frame.size]; |
+} |
+ |
+- (void)dismiss { |
+ [self animatePageInfoViewOut]; |
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, |
+ nil); |
+} |
+ |
+#pragma mark - Helper methods to create subviews. |
+ |
+- (void)addImageViewForInfo:(const PageInfoModel::SectionInfo&)info |
+ toSubviews:(NSMutableArray*)subviews |
+ atOffset:(CGFloat)offset { |
+ CGRect frame = CGRectMake(kFramePadding, offset, kImageSize, kImageSize); |
+ base::scoped_nsobject<UIImageView> imageView( |
+ [[UIImageView alloc] initWithFrame:frame]); |
+ [imageView setImage:model_->GetIconImage(info.icon_id)->ToUIImage()]; |
+ [subviews addObject:imageView.get()]; |
+} |
+ |
+- (CGFloat)addHeadlineViewForInfo:(const PageInfoModel::SectionInfo&)info |
+ toSubviews:(NSMutableArray*)subviews |
+ atPoint:(CGPoint)point { |
+ CGRect frame = CGRectMake(point.x, point.y, textWidth_, kHeadlineHeight); |
+ base::scoped_nsobject<UILabel> label([[UILabel alloc] initWithFrame:frame]); |
+ [label setTextAlignment:NSTextAlignmentNatural]; |
+ [label setText:base::SysUTF16ToNSString(info.headline)]; |
+ [label setTextColor:PageInfoTextColor()]; |
+ [label setFont:PageInfoHeadlineFont()]; |
+ [label setBackgroundColor:[UIColor clearColor]]; |
+ [label setFrame:frame]; |
+ [label setLineBreakMode:NSLineBreakByTruncatingHead]; |
+ [subviews addObject:label.get()]; |
+ return CGRectGetHeight(frame); |
+} |
+ |
+- (CGFloat)addDescriptionViewForInfo:(const PageInfoModel::SectionInfo&)info |
+ toSubviews:(NSMutableArray*)subviews |
+ atPoint:(CGPoint)point { |
+ CGRect frame = CGRectMake(point.x, point.y, textWidth_, kImageSize); |
+ base::scoped_nsobject<UILabel> label([[UILabel alloc] initWithFrame:frame]); |
+ [label setTextAlignment:NSTextAlignmentNatural]; |
+ NSString* description = base::SysUTF16ToNSString(info.description); |
+ UIFont* font = [MDCTypography captionFont]; |
+ [label setTextColor:PageInfoTextColor()]; |
+ [label setText:description]; |
+ [label setFont:font]; |
+ [label setNumberOfLines:0]; |
+ [label setBackgroundColor:[UIColor clearColor]]; |
+ |
+ // If the text is oversized, resize the text field. |
+ CGSize constraintSize = CGSizeMake(textWidth_, CGFLOAT_MAX); |
+ CGSize sizeToFit = |
+ [description cr_boundingSizeWithSize:constraintSize font:font]; |
+ frame.size.height = sizeToFit.height; |
+ [label setFrame:frame]; |
+ [subviews addObject:label.get()]; |
+ return CGRectGetHeight(frame); |
+} |
+ |
+- (UIButton*)buttonForAction:(PageInfoModel::ButtonAction)buttonAction { |
+ if (buttonAction == PageInfoModel::BUTTON_NONE) { |
+ return nil; |
+ } |
+ UIButton* button = [[[UIButton alloc] initWithFrame:CGRectZero] autorelease]; |
+ int messageId = IDS_IOS_PAGE_INFO_RELOAD; |
+ NSInteger tag = IDC_RELOAD; |
+ NSString* accessibilityID = @"Reload button"; |
+ switch (buttonAction) { |
+ case PageInfoModel::BUTTON_NONE: |
+ NOTREACHED(); |
+ return nil; |
+ case PageInfoModel::BUTTON_SHOW_SECURITY_HELP: |
+ messageId = IDS_PAGE_INFO_HELP_CENTER_LINK; |
+ tag = IDC_SHOW_SECURITY_HELP; |
+ accessibilityID = @"What do these mean?"; |
+ break; |
+ case PageInfoModel::BUTTON_RELOAD: |
+ messageId = IDS_IOS_PAGE_INFO_RELOAD; |
+ tag = IDC_RELOAD; |
+ accessibilityID = @"Reload button"; |
+ [button addTarget:self |
+ action:@selector(dismiss) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ break; |
+ }; |
+ |
+ NSString* title = l10n_util::GetNSStringWithFixup(messageId); |
+ SetA11yLabelAndUiAutomationName(button, messageId, accessibilityID); |
+ [button setTitle:title forState:UIControlStateNormal]; |
+ [button setTag:tag]; |
+ [button addTarget:nil |
+ action:@selector(chromeExecuteCommand:) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ return button; |
+} |
+ |
+- (CGFloat)addButton:(PageInfoModel::ButtonAction)buttonAction |
+ toSubviews:(NSMutableArray*)subviews |
+ atOffset:(CGFloat)offset { |
+ UIButton* button = [self buttonForAction:buttonAction]; |
+ if (!button) { |
+ return 0; |
+ } |
+ // The size of the initial frame is irrelevant since it will be changed based |
+ // on the size for the string inside. |
+ CGRect frame = CGRectMake(kButtonXOffset, offset, 100, 10); |
+ |
+ UIFont* font = [MDCTypography captionFont]; |
+ CGSize sizeWithFont = |
+ [[[button titleLabel] text] cr_pixelAlignedSizeWithFont:font]; |
+ frame.size = sizeWithFont; |
+ |
+ [button setFrame:frame]; |
+ |
+ [button.titleLabel setFont:font]; |
+ [button.titleLabel setTextAlignment:NSTextAlignmentLeft]; |
+ [button setTitleColor:PageInfoHelpButtonColor() |
+ forState:UIControlStateNormal]; |
+ [button setTitleColor:PageInfoHelpButtonColor() |
+ forState:UIControlStateSelected]; |
+ [button setBackgroundColor:[UIColor clearColor]]; |
+ |
+ [subviews addObject:button]; |
+ |
+ return CGRectGetHeight([button frame]); |
+} |
+ |
+#pragma mark - UIGestureRecognizerDelegate Implemenation |
+ |
+- (void)rootViewTapped:(UIGestureRecognizer*)sender { |
+ CGPoint pt = [sender locationInView:containerView_]; |
+ if (!CGRectContainsPoint([popupContainer_ frame], pt)) { |
+ [containerView_ chromeExecuteCommand:containerView_]; |
+ } |
+} |
+ |
+- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer |
+ shouldReceiveTouch:(UITouch*)touch { |
+ CGPoint pt = [touch locationInView:containerView_]; |
+ return !CGRectContainsPoint([popupContainer_ frame], pt); |
+} |
+ |
+- (void)animatePageInfoViewIn:(CGRect)source { |
+ // Animate the info card itself. |
+ CATransform3D scaleTransform = PageInfoAnimationScale(); |
+ CGPoint fromPoint = CGPointMake(CGRectGetMidX(source), CGRectGetMidY(source)); |
+ |
+ CABasicAnimation* scaleAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"transform"]; |
+ [scaleAnimation setFromValue:[NSValue valueWithCATransform3D:scaleTransform]]; |
+ |
+ CABasicAnimation* positionAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"position"]; |
+ [positionAnimation setFromValue:[NSValue valueWithCGPoint:fromPoint]]; |
+ |
+ CAAnimationGroup* sizeAnimation = [CAAnimationGroup animation]; |
+ [sizeAnimation setAnimations:@[ scaleAnimation, positionAnimation ]]; |
+ [sizeAnimation |
+ setTimingFunction:TimingFunction(ios::material::CurveEaseInOut)]; |
+ [sizeAnimation setDuration:ios::material::kDuration3]; |
+ |
+ CABasicAnimation* fadeAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"opacity"]; |
+ [fadeAnimation setTimingFunction:TimingFunction(ios::material::CurveEaseOut)]; |
+ [fadeAnimation setDuration:ios::material::kDuration6]; |
+ [fadeAnimation setFromValue:@0]; |
+ [fadeAnimation setToValue:@1]; |
+ |
+ [[popupContainer_ layer] addAnimation:fadeAnimation forKey:@"fade"]; |
+ [[popupContainer_ layer] addAnimation:sizeAnimation forKey:@"size"]; |
+ |
+ // Animation the background grey overlay. |
+ CABasicAnimation* overlayAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"opacity"]; |
+ [overlayAnimation |
+ setTimingFunction:TimingFunction(ios::material::CurveEaseOut)]; |
+ [overlayAnimation setDuration:ios::material::kDuration3]; |
+ [overlayAnimation setFromValue:@0]; |
+ [overlayAnimation setToValue:@1]; |
+ |
+ [[containerView_ layer] addAnimation:overlayAnimation forKey:@"fade"]; |
+ [containerView_ setAlpha:1]; |
+ |
+ // Animate the contents of the info card. |
+ CALayer* contentsLayer = [innerContainerView_ layer]; |
+ |
+ CGRect startFrame = CGRectOffset([innerContainerView_ frame], 0, -32); |
+ CAAnimation* contentSlideAnimation = FrameAnimationMake( |
+ contentsLayer, startFrame, [innerContainerView_ frame]); |
+ [contentSlideAnimation |
+ setTimingFunction:TimingFunction(ios::material::CurveEaseOut)]; |
+ [contentSlideAnimation setDuration:ios::material::kDuration5]; |
+ contentSlideAnimation = |
+ DelayedAnimationMake(contentSlideAnimation, ios::material::kDuration2); |
+ [contentsLayer addAnimation:contentSlideAnimation forKey:@"slide"]; |
+ |
+ [CATransaction begin]; |
+ [CATransaction setCompletionBlock:^{ |
+ [innerContainerView_ setAlpha:1]; |
+ animateInCompleted_ = YES; |
+ }]; |
+ CAAnimation* contentFadeAnimation = OpacityAnimationMake(0.0, 1.0); |
+ [contentFadeAnimation |
+ setTimingFunction:TimingFunction(ios::material::CurveEaseOut)]; |
+ [contentFadeAnimation setDuration:ios::material::kDuration5]; |
+ contentFadeAnimation = |
+ DelayedAnimationMake(contentFadeAnimation, ios::material::kDuration1); |
+ [contentsLayer addAnimation:contentFadeAnimation forKey:@"fade"]; |
+ [CATransaction commit]; |
+ |
+ // Since the animations have delay on them, the alpha of the content view |
+ // needs to be set to zero and then one after the animation starts. If these |
+ // steps are not taken, there will be a visible flash/jump from the initial |
+ // spot during the animation. |
+ [innerContainerView_ setAlpha:0]; |
+} |
+ |
+- (void)animatePageInfoViewOut { |
+ [CATransaction begin]; |
+ [CATransaction setCompletionBlock:^{ |
+ [self.containerView removeFromSuperview]; |
+ }]; |
+ |
+ CABasicAnimation* opacityAnimation = |
+ [CABasicAnimation animationWithKeyPath:@"opacity"]; |
+ [opacityAnimation |
+ setTimingFunction:TimingFunction(ios::material::CurveEaseIn)]; |
+ [opacityAnimation setDuration:ios::material::kDuration3]; |
+ [opacityAnimation setFromValue:@1]; |
+ [opacityAnimation setToValue:@0]; |
+ [[containerView_ layer] addAnimation:opacityAnimation forKey:@"animateOut"]; |
+ |
+ [popupContainer_ setAlpha:0]; |
+ [containerView_ setAlpha:0]; |
+ [CATransaction commit]; |
+} |
+ |
+@end |