Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(80)

Unified Diff: ios/chrome/browser/ui/contextual_search/contextual_search_header_view.mm

Issue 2588713002: Upstream Chrome on iOS source code [4/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/ui/contextual_search/contextual_search_header_view.mm
diff --git a/ios/chrome/browser/ui/contextual_search/contextual_search_header_view.mm b/ios/chrome/browser/ui/contextual_search/contextual_search_header_view.mm
new file mode 100644
index 0000000000000000000000000000000000000000..f156b866fcc44088d0a8f92190aad72580efb915
--- /dev/null
+++ b/ios/chrome/browser/ui/contextual_search/contextual_search_header_view.mm
@@ -0,0 +1,395 @@
+// Copyright 2014 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/contextual_search/contextual_search_header_view.h"
+
+#import "base/ios/weak_nsobject.h"
+#include "base/logging.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_nsobject.h"
+#import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.h"
+#import "ios/chrome/browser/ui/uikit_ui_util.h"
+#import "ios/chrome/common/material_timing.h"
+#include "ios/chrome/common/string_util.h"
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#include "ios/public/provider/chrome/browser/images/branded_image_provider.h"
+#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
+
+namespace {
+const CGFloat kHorizontalMargin = 24.0;
+const CGFloat kHorizontalLayoutGap = 16.0;
+
+const NSTimeInterval kTextTransformAnimationDuration =
+ ios::material::kDuration1;
+const NSTimeInterval kLogoIrisAnimationDuration = ios::material::kDuration1;
+} // namespace
+
+// An image that can "iris" in/out. Assumes a square image and will do a stupid-
+// looking eliptical iris otherwise.
+@interface IrisingImageView : UIImageView
+// |iris| is the degree that the logo is irised; a value of 0.0 indicates
+// the logo is completly invisible, a 1.0 indicates it is completely visible,
+// and 0.5 indicates the iris is open to show a diameter half of the image size.
+// |iris| has an initial value of 1.0.
+// |iris| is animatable, in that setting in inside an animation block will
+// cause the transition to be animated.
+@property(nonatomic, assign) CGFloat iris;
+@end
+
+@implementation IrisingImageView {
+ CGFloat _iris;
+}
+
+@synthesize iris = _iris;
+
+// Create a mask layer for the iris effect
+- (instancetype)initWithImage:(UIImage*)image {
+ if ((self = [super initWithImage:image])) {
+ CAShapeLayer* maskLayer = [CAShapeLayer layer];
+ maskLayer.bounds = self.bounds;
+ base::ScopedCFTypeRef<CGPathRef> path(
+ CGPathCreateWithEllipseInRect(maskLayer.bounds, NULL));
+ maskLayer.path = path;
+ maskLayer.fillColor = [UIColor whiteColor].CGColor;
+ maskLayer.position =
+ CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
+ self.layer.mask = maskLayer;
+ self.iris = 1.0;
+ [self setContentHuggingPriority:UILayoutPriorityDefaultHigh
+ forAxis:UILayoutConstraintAxisVertical];
+ [self setContentHuggingPriority:UILayoutPriorityDefaultHigh
+ forAxis:UILayoutConstraintAxisHorizontal];
+ }
+ return self;
+}
+
+- (void)setIris:(CGFloat)iris {
+ _iris = iris;
+ // Transform the (0.0 ... 1.0) iris value so that the area covered appears
+ // to change linearly. A value of 0.5 should cover half the area of the image,
+ // so the radius of the circle should be sqrt(0.5); so, use sqrt(_iris).
+ // At a scale of 1.0, the iris should totally expose the image, so the iris
+ // diameter must be enough to encompass a square the size of the image; for a
+ // unit square, that's sqrt(2).
+ CGFloat scale = sqrt(_iris) * sqrt(2.0);
+ [self.layer.mask setAffineTransform:CGAffineTransformMakeScale(scale, scale)];
+}
+
+@end
+
+// Button subclass whose intrinsic content size is always large enough to be
+// easily tappable.
+@interface TappableButton : UIButton
+@end
+
+@implementation TappableButton
+
+- (CGSize)intrinsicContentSize {
+ CGSize contentSize = [super intrinsicContentSize];
+ contentSize.height = MAX(contentSize.height, 44.0);
+ contentSize.width = MAX(contentSize.width, 44.0);
+ return contentSize;
+}
+
+@end
+
+@implementation ContextualSearchHeaderView {
+ CGFloat _height;
+ // Circular logo positioned leading side.
+ __unsafe_unretained IrisingImageView* _logo;
+ // Up/down caret positioned trailing side.
+ __unsafe_unretained UIImageView* _caret;
+ // Close control position identically to the caret.
+ __unsafe_unretained TappableButton* _closeButton;
+ // Label showing the text the user tapped on in the web page, and any
+ // additional context that will be displayed.
+ __unsafe_unretained UILabel* _textLabel;
+ base::WeakNSProtocol<id<ContextualSearchPanelTapHandler>> _tapHandler;
+ base::scoped_nsobject<UIGestureRecognizer> _tapRecognizer;
+}
+
++ (BOOL)requiresConstraintBasedLayout {
+ return YES;
+}
+
+- (instancetype)initWithHeight:(CGFloat)height {
+ if (!(self = [super initWithFrame:CGRectZero]))
+ return nil;
+
+ DCHECK(height > 0);
+ _height = height;
+
+ self.translatesAutoresizingMaskIntoConstraints = NO;
+ self.backgroundColor = [UIColor whiteColor];
+ _tapRecognizer.reset([[UITapGestureRecognizer alloc] init]);
+ [self addGestureRecognizer:_tapRecognizer];
+ [_tapRecognizer addTarget:self action:@selector(panelWasTapped:)];
+
+ UIImage* logoImage = ios::GetChromeBrowserProvider()
+ ->GetBrandedImageProvider()
+ ->GetContextualSearchHeaderImage();
+ _logo = [[[IrisingImageView alloc] initWithImage:logoImage] autorelease];
+ _logo.translatesAutoresizingMaskIntoConstraints = NO;
+ _logo.iris = 0.0;
+
+ _caret = [[[UIImageView alloc]
+ initWithImage:[UIImage imageNamed:@"expand_less"]] autorelease];
+ _caret.translatesAutoresizingMaskIntoConstraints = NO;
+ [_caret setContentHuggingPriority:UILayoutPriorityDefaultHigh
+ forAxis:UILayoutConstraintAxisVertical];
+ [_caret setContentHuggingPriority:UILayoutPriorityDefaultHigh
+ forAxis:UILayoutConstraintAxisHorizontal];
+
+ _closeButton =
+ [[[TappableButton alloc] initWithFrame:CGRectZero] autorelease];
+ _closeButton.translatesAutoresizingMaskIntoConstraints = NO;
+ [_closeButton setImage:[UIImage imageNamed:@"card_close_button"]
+ forState:UIControlStateNormal];
+ [_closeButton setImage:[UIImage imageNamed:@"card_close_button_pressed"]
+ forState:UIControlStateHighlighted];
+ [_closeButton setContentHuggingPriority:UILayoutPriorityDefaultHigh
+ forAxis:UILayoutConstraintAxisVertical];
+ [_closeButton setContentHuggingPriority:UILayoutPriorityDefaultHigh
+ forAxis:UILayoutConstraintAxisHorizontal];
+ _closeButton.alpha = 0;
+
+ _textLabel = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease];
+ _textLabel.translatesAutoresizingMaskIntoConstraints = NO;
+ _textLabel.font = [MDCTypography subheadFont];
+ _textLabel.textAlignment = NSTextAlignmentNatural;
+ _textLabel.lineBreakMode = NSLineBreakByCharWrapping;
+ // Ensure that |_textLabel| doesn't expand past the space defined for it
+ // regardless of how long its text is.
+ [_textLabel setContentHuggingPriority:UILayoutPriorityDefaultLow
+ forAxis:UILayoutConstraintAxisHorizontal];
+
+ [self setAccessibilityIdentifier:@"header"];
+ [_logo setAccessibilityIdentifier:@"logo"];
+ [_caret setAccessibilityIdentifier:@"caret"];
+ [_closeButton setAccessibilityIdentifier:@"close"];
+ [_textLabel setAccessibilityIdentifier:@"selectedText"];
+
+ [self addSubview:_logo];
+ [self addSubview:_caret];
+ [self addSubview:_textLabel];
+ [self addSubview:_closeButton];
+
+ [self setLayoutMargins:UIEdgeInsetsMake(0, kHorizontalMargin, 0,
+ kHorizontalMargin)];
+
+ [NSLayoutConstraint activateConstraints:@[
+ // Horizontal layout:
+ // Logo is at the leading margin:
+ [_logo.leadingAnchor
+ constraintEqualToAnchor:self.layoutMarginsGuide.leadingAnchor],
+ // Caret is at the trailing margin:
+ [_caret.trailingAnchor
+ constraintEqualToAnchor:self.layoutMarginsGuide.trailingAnchor],
+ // Close button is centered over the caret:
+ [_closeButton.centerXAnchor constraintEqualToAnchor:_caret.centerXAnchor],
+ // The available space for the text label is the space (minus
+ // |kHorizontalLayoutGap| on each side) between the logo and the caret:
+ [_textLabel.leadingAnchor constraintEqualToAnchor:_logo.trailingAnchor
+ constant:kHorizontalLayoutGap],
+ [_textLabel.trailingAnchor
+ constraintLessThanOrEqualToAnchor:_caret.leadingAnchor
+ constant:-kHorizontalLayoutGap],
+ // Vertical layout:
+ // Everything is center-aligned to |self|.
+ [_logo.centerYAnchor
+ constraintEqualToAnchor:self.layoutMarginsGuide.centerYAnchor],
+ [_textLabel.centerYAnchor
+ constraintEqualToAnchor:self.layoutMarginsGuide.centerYAnchor],
+ [_caret.centerYAnchor
+ constraintEqualToAnchor:self.layoutMarginsGuide.centerYAnchor],
+ [_closeButton.centerYAnchor constraintEqualToAnchor:_caret.centerYAnchor],
+ ]];
+
+ return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE {
+ NOTREACHED();
+ return nil;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE {
+ NOTREACHED();
+ return nil;
+}
+
+#pragma mark - property implementation.
+
+- (void)setTapHandler:(id<ContextualSearchPanelTapHandler>)tapHandler {
+ if (_tapHandler) {
+ [_closeButton removeTarget:_tapHandler
+ action:@selector(closePanel)
+ forControlEvents:UIControlEventTouchUpInside];
+ }
+ _tapHandler.reset(tapHandler);
+ if (_tapHandler) {
+ [_closeButton addTarget:_tapHandler
+ action:@selector(closePanel)
+ forControlEvents:UIControlEventTouchUpInside];
+ }
+}
+
+- (id<ContextualSearchPanelTapHandler>)tapHandler {
+ return _tapHandler;
+}
+
+- (void)panelWasTapped:(UIGestureRecognizer*)gestureRecognizer {
+ for (NSUInteger touchIndex = 0;
+ touchIndex < gestureRecognizer.numberOfTouches; touchIndex++) {
+ if (!CGRectContainsPoint(
+ self.frame,
+ [gestureRecognizer locationOfTouch:touchIndex inView:self])) {
+ return;
+ }
+ }
+ [_tapHandler panelWasTapped:gestureRecognizer];
+}
+
+#pragma mark - UIView layout methods
+
+- (CGSize)intrinsicContentSize {
+ // This view's height is always |_height|.
+ return CGSizeMake(UIViewNoIntrinsicMetric, _height);
+}
+
+#pragma mark - ContextualSearchPanelMotionObserver
+
+- (void)panel:(ContextualSearchPanelView*)panel
+ didMoveWithMotion:(ContextualSearch::PanelMotion)motion {
+ if (motion.state == ContextualSearch::PREVIEWING) {
+ [self setCloseButtonTransition:1.0];
+ }
+ if (motion.state == ContextualSearch::PEEKING) {
+ _caret.alpha = 1.0;
+ [self setCloseButtonTransition:motion.gradation];
+ }
+}
+
+- (void)panelWillPromote:(ContextualSearchPanelView*)panel {
+ // Disable tap handling.
+ self.tapHandler = nil;
+}
+
+- (void)panelIsPromoting:(ContextualSearchPanelView*)panel {
+ self.alpha = 0.0;
+ [panel removeMotionObserver:self];
+}
+
+#pragma mark - Subview update
+
+- (void)setCloseButtonTransition:(CGFloat)gradation {
+ // Crossfade the caret into the close button by fading the caret all the way
+ // out, and then fading the close button in.
+ // As the overall gradation moves from 0 to 0.5, the caret's alpha moves
+ // from 1.0 to 0, and as the gradation continues from 0.5 to 1.0, the
+ // close button's alpha moves from 0 to 1.0.
+ CGFloat scaledGradation = 1 - (2 * gradation); // [0, 1] -> [1, -1]
+ CGFloat caretGradation = MAX(scaledGradation, 0); // [1.0 .. 0.0 .. 0.0]
+ CGFloat closeGradation = MAX(-scaledGradation, 0); // [0.0 .. 0.0 .. 1.0]
+ _caret.alpha = caretGradation * caretGradation;
+ _closeButton.alpha = closeGradation * closeGradation;
+}
+
+#pragma mark - Animated transitions
+
+- (void)setText:(NSString*)text
+ followingTextRange:(NSRange)followingTextRange
+ animated:(BOOL)animated {
+ NSMutableAttributedString* styledText =
+ [[[NSMutableAttributedString alloc] initWithString:text] autorelease];
+ [styledText addAttribute:NSForegroundColorAttributeName
+ value:[UIColor colorWithWhite:0 alpha:0.71f]
+ range:followingTextRange];
+
+ void (^transform)(void) = ^{
+ _textLabel.attributedText = styledText;
+ };
+ void (^complete)(BOOL) = ^(BOOL finished) {
+ [self showLogoAnimated:animated];
+ };
+
+ if (animated) {
+ UIViewAnimationOptions options =
+ UIViewAnimationOptionTransitionCrossDissolve;
+ [UIView cr_transitionWithView:self
+ duration:kTextTransformAnimationDuration
+ curve:ios::material::CurveEaseOut
+ options:options
+ animations:transform
+ completion:complete];
+ } else {
+ transform();
+ complete(NO);
+ }
+}
+
+- (void)setSearchTerm:(NSString*)searchTerm animated:(BOOL)animated {
+ void (^transform)(void) = ^{
+ _textLabel.text = searchTerm;
+ };
+ void (^complete)(BOOL) = ^(BOOL finished) {
+ [self showLogoAnimated:animated];
+ };
+
+ if (animated) {
+ UIViewAnimationOptions options =
+ UIViewAnimationOptionTransitionCrossDissolve;
+ [UIView cr_transitionWithView:self
+ duration:kTextTransformAnimationDuration
+ curve:ios::material::CurveEaseInOut
+ options:options
+ animations:transform
+ completion:complete];
+ } else {
+ transform();
+ complete(NO);
+ }
+}
+
+- (void)showLogoAnimated:(BOOL)animated {
+ // Since the logo is round, we only need to animate to 1/sqrt(2) to display
+ // the whole thing.
+ if ([_logo iris] > 0.0)
+ return;
+
+ void (^transform)(void) = ^{
+ [_logo setIris:(1.0 / sqrt(2.0))];
+ };
+ if (animated) {
+ [UIView cr_animateWithDuration:kLogoIrisAnimationDuration
+ delay:0
+ curve:ios::material::CurveEaseIn
+ options:0
+ animations:transform
+ completion:nil];
+ } else {
+ transform();
+ }
+}
+
+- (void)hideLogoAnimated:(BOOL)animated {
+ if ([_logo iris] == 0.0)
+ return;
+
+ void (^transform)(void) = ^{
+ [_logo setIris:0.0];
+ };
+ if (animated) {
+ [UIView cr_animateWithDuration:kLogoIrisAnimationDuration
+ delay:0
+ curve:ios::material::CurveEaseOut
+ options:0
+ animations:transform
+ completion:nil];
+ } else {
+ transform();
+ }
+}
+
+@end

Powered by Google App Engine
This is Rietveld 408576698