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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 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 "ios/chrome/browser/ui/contextual_search/contextual_search_header_view.h "
6
7 #import "base/ios/weak_nsobject.h"
8 #include "base/logging.h"
9 #include "base/mac/scoped_cftyperef.h"
10 #include "base/mac/scoped_nsobject.h"
11 #import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.h"
12 #import "ios/chrome/browser/ui/uikit_ui_util.h"
13 #import "ios/chrome/common/material_timing.h"
14 #include "ios/chrome/common/string_util.h"
15 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
16 #include "ios/public/provider/chrome/browser/images/branded_image_provider.h"
17 #import "ios/third_party/material_components_ios/src/components/Typography/src/M aterialTypography.h"
18
19 namespace {
20 const CGFloat kHorizontalMargin = 24.0;
21 const CGFloat kHorizontalLayoutGap = 16.0;
22
23 const NSTimeInterval kTextTransformAnimationDuration =
24 ios::material::kDuration1;
25 const NSTimeInterval kLogoIrisAnimationDuration = ios::material::kDuration1;
26 } // namespace
27
28 // An image that can "iris" in/out. Assumes a square image and will do a stupid-
29 // looking eliptical iris otherwise.
30 @interface IrisingImageView : UIImageView
31 // |iris| is the degree that the logo is irised; a value of 0.0 indicates
32 // the logo is completly invisible, a 1.0 indicates it is completely visible,
33 // and 0.5 indicates the iris is open to show a diameter half of the image size.
34 // |iris| has an initial value of 1.0.
35 // |iris| is animatable, in that setting in inside an animation block will
36 // cause the transition to be animated.
37 @property(nonatomic, assign) CGFloat iris;
38 @end
39
40 @implementation IrisingImageView {
41 CGFloat _iris;
42 }
43
44 @synthesize iris = _iris;
45
46 // Create a mask layer for the iris effect
47 - (instancetype)initWithImage:(UIImage*)image {
48 if ((self = [super initWithImage:image])) {
49 CAShapeLayer* maskLayer = [CAShapeLayer layer];
50 maskLayer.bounds = self.bounds;
51 base::ScopedCFTypeRef<CGPathRef> path(
52 CGPathCreateWithEllipseInRect(maskLayer.bounds, NULL));
53 maskLayer.path = path;
54 maskLayer.fillColor = [UIColor whiteColor].CGColor;
55 maskLayer.position =
56 CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
57 self.layer.mask = maskLayer;
58 self.iris = 1.0;
59 [self setContentHuggingPriority:UILayoutPriorityDefaultHigh
60 forAxis:UILayoutConstraintAxisVertical];
61 [self setContentHuggingPriority:UILayoutPriorityDefaultHigh
62 forAxis:UILayoutConstraintAxisHorizontal];
63 }
64 return self;
65 }
66
67 - (void)setIris:(CGFloat)iris {
68 _iris = iris;
69 // Transform the (0.0 ... 1.0) iris value so that the area covered appears
70 // to change linearly. A value of 0.5 should cover half the area of the image,
71 // so the radius of the circle should be sqrt(0.5); so, use sqrt(_iris).
72 // At a scale of 1.0, the iris should totally expose the image, so the iris
73 // diameter must be enough to encompass a square the size of the image; for a
74 // unit square, that's sqrt(2).
75 CGFloat scale = sqrt(_iris) * sqrt(2.0);
76 [self.layer.mask setAffineTransform:CGAffineTransformMakeScale(scale, scale)];
77 }
78
79 @end
80
81 // Button subclass whose intrinsic content size is always large enough to be
82 // easily tappable.
83 @interface TappableButton : UIButton
84 @end
85
86 @implementation TappableButton
87
88 - (CGSize)intrinsicContentSize {
89 CGSize contentSize = [super intrinsicContentSize];
90 contentSize.height = MAX(contentSize.height, 44.0);
91 contentSize.width = MAX(contentSize.width, 44.0);
92 return contentSize;
93 }
94
95 @end
96
97 @implementation ContextualSearchHeaderView {
98 CGFloat _height;
99 // Circular logo positioned leading side.
100 __unsafe_unretained IrisingImageView* _logo;
101 // Up/down caret positioned trailing side.
102 __unsafe_unretained UIImageView* _caret;
103 // Close control position identically to the caret.
104 __unsafe_unretained TappableButton* _closeButton;
105 // Label showing the text the user tapped on in the web page, and any
106 // additional context that will be displayed.
107 __unsafe_unretained UILabel* _textLabel;
108 base::WeakNSProtocol<id<ContextualSearchPanelTapHandler>> _tapHandler;
109 base::scoped_nsobject<UIGestureRecognizer> _tapRecognizer;
110 }
111
112 + (BOOL)requiresConstraintBasedLayout {
113 return YES;
114 }
115
116 - (instancetype)initWithHeight:(CGFloat)height {
117 if (!(self = [super initWithFrame:CGRectZero]))
118 return nil;
119
120 DCHECK(height > 0);
121 _height = height;
122
123 self.translatesAutoresizingMaskIntoConstraints = NO;
124 self.backgroundColor = [UIColor whiteColor];
125 _tapRecognizer.reset([[UITapGestureRecognizer alloc] init]);
126 [self addGestureRecognizer:_tapRecognizer];
127 [_tapRecognizer addTarget:self action:@selector(panelWasTapped:)];
128
129 UIImage* logoImage = ios::GetChromeBrowserProvider()
130 ->GetBrandedImageProvider()
131 ->GetContextualSearchHeaderImage();
132 _logo = [[[IrisingImageView alloc] initWithImage:logoImage] autorelease];
133 _logo.translatesAutoresizingMaskIntoConstraints = NO;
134 _logo.iris = 0.0;
135
136 _caret = [[[UIImageView alloc]
137 initWithImage:[UIImage imageNamed:@"expand_less"]] autorelease];
138 _caret.translatesAutoresizingMaskIntoConstraints = NO;
139 [_caret setContentHuggingPriority:UILayoutPriorityDefaultHigh
140 forAxis:UILayoutConstraintAxisVertical];
141 [_caret setContentHuggingPriority:UILayoutPriorityDefaultHigh
142 forAxis:UILayoutConstraintAxisHorizontal];
143
144 _closeButton =
145 [[[TappableButton alloc] initWithFrame:CGRectZero] autorelease];
146 _closeButton.translatesAutoresizingMaskIntoConstraints = NO;
147 [_closeButton setImage:[UIImage imageNamed:@"card_close_button"]
148 forState:UIControlStateNormal];
149 [_closeButton setImage:[UIImage imageNamed:@"card_close_button_pressed"]
150 forState:UIControlStateHighlighted];
151 [_closeButton setContentHuggingPriority:UILayoutPriorityDefaultHigh
152 forAxis:UILayoutConstraintAxisVertical];
153 [_closeButton setContentHuggingPriority:UILayoutPriorityDefaultHigh
154 forAxis:UILayoutConstraintAxisHorizontal];
155 _closeButton.alpha = 0;
156
157 _textLabel = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease];
158 _textLabel.translatesAutoresizingMaskIntoConstraints = NO;
159 _textLabel.font = [MDCTypography subheadFont];
160 _textLabel.textAlignment = NSTextAlignmentNatural;
161 _textLabel.lineBreakMode = NSLineBreakByCharWrapping;
162 // Ensure that |_textLabel| doesn't expand past the space defined for it
163 // regardless of how long its text is.
164 [_textLabel setContentHuggingPriority:UILayoutPriorityDefaultLow
165 forAxis:UILayoutConstraintAxisHorizontal];
166
167 [self setAccessibilityIdentifier:@"header"];
168 [_logo setAccessibilityIdentifier:@"logo"];
169 [_caret setAccessibilityIdentifier:@"caret"];
170 [_closeButton setAccessibilityIdentifier:@"close"];
171 [_textLabel setAccessibilityIdentifier:@"selectedText"];
172
173 [self addSubview:_logo];
174 [self addSubview:_caret];
175 [self addSubview:_textLabel];
176 [self addSubview:_closeButton];
177
178 [self setLayoutMargins:UIEdgeInsetsMake(0, kHorizontalMargin, 0,
179 kHorizontalMargin)];
180
181 [NSLayoutConstraint activateConstraints:@[
182 // Horizontal layout:
183 // Logo is at the leading margin:
184 [_logo.leadingAnchor
185 constraintEqualToAnchor:self.layoutMarginsGuide.leadingAnchor],
186 // Caret is at the trailing margin:
187 [_caret.trailingAnchor
188 constraintEqualToAnchor:self.layoutMarginsGuide.trailingAnchor],
189 // Close button is centered over the caret:
190 [_closeButton.centerXAnchor constraintEqualToAnchor:_caret.centerXAnchor],
191 // The available space for the text label is the space (minus
192 // |kHorizontalLayoutGap| on each side) between the logo and the caret:
193 [_textLabel.leadingAnchor constraintEqualToAnchor:_logo.trailingAnchor
194 constant:kHorizontalLayoutGap],
195 [_textLabel.trailingAnchor
196 constraintLessThanOrEqualToAnchor:_caret.leadingAnchor
197 constant:-kHorizontalLayoutGap],
198 // Vertical layout:
199 // Everything is center-aligned to |self|.
200 [_logo.centerYAnchor
201 constraintEqualToAnchor:self.layoutMarginsGuide.centerYAnchor],
202 [_textLabel.centerYAnchor
203 constraintEqualToAnchor:self.layoutMarginsGuide.centerYAnchor],
204 [_caret.centerYAnchor
205 constraintEqualToAnchor:self.layoutMarginsGuide.centerYAnchor],
206 [_closeButton.centerYAnchor constraintEqualToAnchor:_caret.centerYAnchor],
207 ]];
208
209 return self;
210 }
211
212 - (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE {
213 NOTREACHED();
214 return nil;
215 }
216
217 - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE {
218 NOTREACHED();
219 return nil;
220 }
221
222 #pragma mark - property implementation.
223
224 - (void)setTapHandler:(id<ContextualSearchPanelTapHandler>)tapHandler {
225 if (_tapHandler) {
226 [_closeButton removeTarget:_tapHandler
227 action:@selector(closePanel)
228 forControlEvents:UIControlEventTouchUpInside];
229 }
230 _tapHandler.reset(tapHandler);
231 if (_tapHandler) {
232 [_closeButton addTarget:_tapHandler
233 action:@selector(closePanel)
234 forControlEvents:UIControlEventTouchUpInside];
235 }
236 }
237
238 - (id<ContextualSearchPanelTapHandler>)tapHandler {
239 return _tapHandler;
240 }
241
242 - (void)panelWasTapped:(UIGestureRecognizer*)gestureRecognizer {
243 for (NSUInteger touchIndex = 0;
244 touchIndex < gestureRecognizer.numberOfTouches; touchIndex++) {
245 if (!CGRectContainsPoint(
246 self.frame,
247 [gestureRecognizer locationOfTouch:touchIndex inView:self])) {
248 return;
249 }
250 }
251 [_tapHandler panelWasTapped:gestureRecognizer];
252 }
253
254 #pragma mark - UIView layout methods
255
256 - (CGSize)intrinsicContentSize {
257 // This view's height is always |_height|.
258 return CGSizeMake(UIViewNoIntrinsicMetric, _height);
259 }
260
261 #pragma mark - ContextualSearchPanelMotionObserver
262
263 - (void)panel:(ContextualSearchPanelView*)panel
264 didMoveWithMotion:(ContextualSearch::PanelMotion)motion {
265 if (motion.state == ContextualSearch::PREVIEWING) {
266 [self setCloseButtonTransition:1.0];
267 }
268 if (motion.state == ContextualSearch::PEEKING) {
269 _caret.alpha = 1.0;
270 [self setCloseButtonTransition:motion.gradation];
271 }
272 }
273
274 - (void)panelWillPromote:(ContextualSearchPanelView*)panel {
275 // Disable tap handling.
276 self.tapHandler = nil;
277 }
278
279 - (void)panelIsPromoting:(ContextualSearchPanelView*)panel {
280 self.alpha = 0.0;
281 [panel removeMotionObserver:self];
282 }
283
284 #pragma mark - Subview update
285
286 - (void)setCloseButtonTransition:(CGFloat)gradation {
287 // Crossfade the caret into the close button by fading the caret all the way
288 // out, and then fading the close button in.
289 // As the overall gradation moves from 0 to 0.5, the caret's alpha moves
290 // from 1.0 to 0, and as the gradation continues from 0.5 to 1.0, the
291 // close button's alpha moves from 0 to 1.0.
292 CGFloat scaledGradation = 1 - (2 * gradation); // [0, 1] -> [1, -1]
293 CGFloat caretGradation = MAX(scaledGradation, 0); // [1.0 .. 0.0 .. 0.0]
294 CGFloat closeGradation = MAX(-scaledGradation, 0); // [0.0 .. 0.0 .. 1.0]
295 _caret.alpha = caretGradation * caretGradation;
296 _closeButton.alpha = closeGradation * closeGradation;
297 }
298
299 #pragma mark - Animated transitions
300
301 - (void)setText:(NSString*)text
302 followingTextRange:(NSRange)followingTextRange
303 animated:(BOOL)animated {
304 NSMutableAttributedString* styledText =
305 [[[NSMutableAttributedString alloc] initWithString:text] autorelease];
306 [styledText addAttribute:NSForegroundColorAttributeName
307 value:[UIColor colorWithWhite:0 alpha:0.71f]
308 range:followingTextRange];
309
310 void (^transform)(void) = ^{
311 _textLabel.attributedText = styledText;
312 };
313 void (^complete)(BOOL) = ^(BOOL finished) {
314 [self showLogoAnimated:animated];
315 };
316
317 if (animated) {
318 UIViewAnimationOptions options =
319 UIViewAnimationOptionTransitionCrossDissolve;
320 [UIView cr_transitionWithView:self
321 duration:kTextTransformAnimationDuration
322 curve:ios::material::CurveEaseOut
323 options:options
324 animations:transform
325 completion:complete];
326 } else {
327 transform();
328 complete(NO);
329 }
330 }
331
332 - (void)setSearchTerm:(NSString*)searchTerm animated:(BOOL)animated {
333 void (^transform)(void) = ^{
334 _textLabel.text = searchTerm;
335 };
336 void (^complete)(BOOL) = ^(BOOL finished) {
337 [self showLogoAnimated:animated];
338 };
339
340 if (animated) {
341 UIViewAnimationOptions options =
342 UIViewAnimationOptionTransitionCrossDissolve;
343 [UIView cr_transitionWithView:self
344 duration:kTextTransformAnimationDuration
345 curve:ios::material::CurveEaseInOut
346 options:options
347 animations:transform
348 completion:complete];
349 } else {
350 transform();
351 complete(NO);
352 }
353 }
354
355 - (void)showLogoAnimated:(BOOL)animated {
356 // Since the logo is round, we only need to animate to 1/sqrt(2) to display
357 // the whole thing.
358 if ([_logo iris] > 0.0)
359 return;
360
361 void (^transform)(void) = ^{
362 [_logo setIris:(1.0 / sqrt(2.0))];
363 };
364 if (animated) {
365 [UIView cr_animateWithDuration:kLogoIrisAnimationDuration
366 delay:0
367 curve:ios::material::CurveEaseIn
368 options:0
369 animations:transform
370 completion:nil];
371 } else {
372 transform();
373 }
374 }
375
376 - (void)hideLogoAnimated:(BOOL)animated {
377 if ([_logo iris] == 0.0)
378 return;
379
380 void (^transform)(void) = ^{
381 [_logo setIris:0.0];
382 };
383 if (animated) {
384 [UIView cr_animateWithDuration:kLogoIrisAnimationDuration
385 delay:0
386 curve:ios::material::CurveEaseOut
387 options:0
388 animations:transform
389 completion:nil];
390 } else {
391 transform();
392 }
393 }
394
395 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698